apex-named-credentials-patterns

Use when writing Apex that calls out to external endpoints via Named Credentials, working with custom header formula tokens ({!$Credential.OAuthToken}), querying per-user auth state through the UserExternalCredential SObject, or diagnosing why Named Credential callouts fail. Trigger keywords: 'callout: prefix', 'named credential header formula', 'UserExternalCredential', 'External Credential per-user principal', 'Named Credential oauth token apex'. NOT for Named Credential setup in the Salesforce Setup UI — use integration/named-credentials-setup. NOT for general HTTP callout mechanics (HttpRequest, HttpResponse, mock patterns) — use integration/callouts-and-http-integrations.

Best use case

apex-named-credentials-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when writing Apex that calls out to external endpoints via Named Credentials, working with custom header formula tokens ({!$Credential.OAuthToken}), querying per-user auth state through the UserExternalCredential SObject, or diagnosing why Named Credential callouts fail. Trigger keywords: 'callout: prefix', 'named credential header formula', 'UserExternalCredential', 'External Credential per-user principal', 'Named Credential oauth token apex'. NOT for Named Credential setup in the Salesforce Setup UI — use integration/named-credentials-setup. NOT for general HTTP callout mechanics (HttpRequest, HttpResponse, mock patterns) — use integration/callouts-and-http-integrations.

Teams using apex-named-credentials-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-named-credentials-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-named-credentials-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-named-credentials-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-named-credentials-patterns Compares

Feature / Agentapex-named-credentials-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when writing Apex that calls out to external endpoints via Named Credentials, working with custom header formula tokens ({!$Credential.OAuthToken}), querying per-user auth state through the UserExternalCredential SObject, or diagnosing why Named Credential callouts fail. Trigger keywords: 'callout: prefix', 'named credential header formula', 'UserExternalCredential', 'External Credential per-user principal', 'Named Credential oauth token apex'. NOT for Named Credential setup in the Salesforce Setup UI — use integration/named-credentials-setup. NOT for general HTTP callout mechanics (HttpRequest, HttpResponse, mock patterns) — use integration/callouts-and-http-integrations.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Apex Named Credentials Patterns

Use this skill when writing Apex that makes authenticated outbound callouts through Named Credentials — covering the `callout:` URL prefix, custom header formula tokens for injecting OAuth or Basic auth values, the `UserExternalCredential` SObject API for per-user token inspection, and the behavioral differences between the legacy and enhanced Named Credential models that affect Apex code.

---

## Before Starting

- Is the org using the **legacy model** (single Named Credential with embedded auth config) or the **enhanced model** (External Credential for auth + Named Credential for endpoint, introduced Spring '22)?  The two models have different metadata shapes but the same `callout:` URL syntax in Apex.
- Does the use case require **per-user auth** (each user authenticates individually with OAuth) or **org-wide auth** (all users share one credential set)? Per-user auth requires querying `UserExternalCredential` to check whether a given user has an active token before making the callout.
- Are there **custom header fields** defined on the Named Credential that inject OAuth tokens or credentials? These use the `{!$Credential.*}` formula syntax and only resolve at callout time — they are not available inside Apex code at compile or run time.
- Named Credentials automatically exempt the endpoint host from Remote Site Settings. In **managed packages**, the subscriber org may still need Remote Site Settings if the Named Credential is not part of the package. Confirm the distribution model before relying on this exemption.

---

## Core Concepts

### Legacy vs. Enhanced Named Credential Model

The **legacy model** combines the endpoint URL and authentication configuration in a single Named Credential metadata record. The **enhanced model** (Spring '22+) separates them:

- **External Credential** — holds the authentication protocol, OAuth flow configuration, principals, and per-user identity mapping.
- **Named Credential** — holds the endpoint URL and references an External Credential.

From **Apex code, both models use exactly the same `callout:` URL syntax**. The difference is invisible to the Apex developer in normal callout code. The split matters when you need to inspect or manage per-user auth state via the `UserExternalCredential` SObject (enhanced model only) or when configuring multiple Named Credentials that share the same auth config.

### The `callout:` URL Prefix

Named Credentials are referenced in Apex via the `callout:` URL prefix. The syntax is:

```
callout:<Named_Credential_API_Name>[/path][?query_string]
```

The Named Credential API name is case-sensitive. The trailing path is optional; the Named Credential endpoint URL and the Apex-supplied path are concatenated at runtime by the platform.

```apex
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:MyServiceNC/api/v2/customers');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
```

The platform injects authentication headers, resolves formula tokens in custom headers, and validates Remote Site Settings on the endpoint automatically. **You must never construct the full endpoint URL with credentials hard-coded.**

### Custom Header Formula Tokens

Named Credential records (both models) support **custom header fields** that can embed runtime-resolved formula tokens. These are set in the Named Credential setup UI, not in Apex. At callout time, the platform evaluates these formulas and injects the resulting values as HTTP headers.

Available formula tokens (Apex Developer Guide — Named Credentials):

| Token | Resolves to |
|---|---|
| `{!$Credential.OAuthToken}` | The active OAuth access token for the current user or org-wide principal |
| `{!$Credential.Username}` | The username stored in the credential |
| `{!$Credential.Password}` | The password stored in the credential |

**Key constraint:** Formula tokens are evaluated by the platform at callout time on the server side. They **cannot be read back in Apex code** — you cannot call `System.Label` or any getter to obtain the resolved token value in your Apex logic. They are write-only from the Apex developer's perspective.

**Formula tokens are only valid inside Named Credential custom header fields.** Attempting to use `{!$Credential.OAuthToken}` inside an Apex string literal, a request body, or a URL path does not resolve — the literal string is sent verbatim.

### UserExternalCredential SObject (Enhanced Model)

The `UserExternalCredential` SObject exists only in orgs using the enhanced model. It records the per-user auth association between a Salesforce User and an External Credential Principal. Querying it lets Apex determine whether a specific user has an active (or any) token before initiating a callout on their behalf.

Key fields:

| Field | Description |
|---|---|
| `ExternalCredentialId` | 18-char ID of the External Credential record |
| `UserId` | 18-char ID of the Salesforce User |
| `PrincipalType` | `NamedPrincipal` or `PerUserPrincipal` |

A `UserExternalCredential` record existing for a user indicates the user has completed the OAuth flow. Absence means the user has not authenticated and the callout will fail with an auth error. Use this SObject to gate callouts or present the user with a re-authentication prompt.

### Callout Limits and Constraints

Named Credential callouts are subject to the same Apex callout limits as all other callouts (Apex Developer Guide — Callout Limits):

- Maximum callout timeout: **120 seconds** (10 seconds default if not explicitly set via `req.setTimeout()`).
- Maximum callouts per transaction: **100**.
- Named Credentials are **not compatible with the Continuation framework** (async Visualforce/Aura callouts). Continuation requires a full URL endpoint with Remote Site Settings; the `callout:` prefix is not supported.

---

## Common Patterns

### Callout With Named Credential and Custom OAuth Header Injection

**When to use:** The external API requires the OAuth access token in a custom header (e.g., `X-API-Token`) rather than the standard `Authorization` header, and the Named Credential has a custom header field with `{!$Credential.OAuthToken}` configured.

**How it works:** The Named Credential admin configures a custom header (e.g., `X-API-Token`) with the formula `{!$Credential.OAuthToken}` in the Setup UI. In Apex, the developer references the Named Credential with the standard `callout:` prefix. The platform evaluates the formula and injects the header automatically.

```apex
public class CustomerServiceCallout {
    public static HttpResponse fetchCustomer(String customerId) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:CustomerServiceNC/api/v1/customers/' + customerId);
        req.setMethod('GET');
        req.setTimeout(30000); // 30 seconds; max is 120000

        // No explicit auth header needed here.
        // The Named Credential's custom header formula {!$Credential.OAuthToken}
        // injects the OAuth token at callout time.

        HttpResponse res = new Http().send(req);
        return res;
    }
}
```

**Why not the alternative:** Do not manually retrieve the token from a Custom Setting or Custom Metadata and set `req.setHeader('Authorization', 'Bearer ' + token)`. This stores credentials in the Salesforce database outside the Protected credential vault, bypasses token refresh handling, and is an anti-pattern per the Salesforce Well-Architected Security pillar.

### Per-User Token Status Check via UserExternalCredential

**When to use:** The integration uses per-user OAuth and the UX should prompt the user to authenticate if they have not yet completed the OAuth flow, rather than letting a callout fail with a cryptic auth error.

**How it works:** Query `UserExternalCredential` for the current user and the target External Credential ID before making the callout. If no record exists, surface a re-auth URL or a friendly error.

```apex
public class AuthStatusChecker {
    /**
     * Returns true if the current user has an active UserExternalCredential
     * for the given External Credential API name.
     *
     * @param externalCredentialApiName  e.g. 'CustomerService_EC'
     */
    public static Boolean isUserAuthenticated(String externalCredentialApiName) {
        // Resolve the External Credential ID from the API name.
        // ExternalCredential is a setup object — use SOQL on it.
        List<ExternalCredential> ecs = [
            SELECT Id
            FROM ExternalCredential
            WHERE DeveloperName = :externalCredentialApiName
            LIMIT 1
        ];
        if (ecs.isEmpty()) {
            return false;
        }
        Id ecId = ecs[0].Id;

        // Check if the current user has a UserExternalCredential record
        // with PerUserPrincipal type.
        List<UserExternalCredential> uecs = [
            SELECT Id, PrincipalType
            FROM UserExternalCredential
            WHERE UserId = :UserInfo.getUserId()
              AND ExternalCredentialId = :ecId
              AND PrincipalType = 'PerUserPrincipal'
            LIMIT 1
        ];
        return !uecs.isEmpty();
    }
}
```

**Why not the alternative:** Do not attempt the callout and inspect the HTTP 401 response status as a proxy for "user not authenticated." That approach wastes a callout, can confuse application-level 401s (the API rejecting the request) with auth-layer 401s (no token available), and counts against the 100-callout-per-transaction limit.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Standard REST callout to authenticated endpoint | `callout:NCName/path` in `setEndpoint()` | Platform injects auth, handles Remote Site Settings |
| OAuth token must appear in a custom header, not Authorization | Configure formula `{!$Credential.OAuthToken}` in Named Credential custom header field | Formula is evaluated at callout time; no Apex token handling needed |
| Need to check if current user has authenticated (per-user OAuth) | Query `UserExternalCredential` for UserId + ExternalCredentialId before callout | Avoids callout failure; enables proactive re-auth prompt |
| Async Visualforce or Aura page needing callout | Use standard synchronous callout with `callout:` prefix; Continuation framework is NOT supported | Continuation does not support the `callout:` prefix |
| Legacy model org, single Named Credential | Same `callout:` syntax; no `UserExternalCredential` SObject available | Behavioral model differs; `UserExternalCredential` is enhanced model only |
| Managed package distribution | Verify subscriber org Remote Site Settings if Named Credential not included in package | Named Credentials in packages do not automatically provision RSSettings in subscriber |
| Token formula in request body or URL path | Not supported — use an alternative approach (custom header field only) | `{!$Credential.*}` tokens only resolve in Named Credential custom header fields |

---

## Recommended Workflow

1. **Confirm the Named Credential model** — check Setup > Named Credentials and determine whether the org uses the legacy model (single record) or the enhanced model (External Credential + Named Credential pair). This determines whether `UserExternalCredential` is available and whether custom header formulas are configured on the Named Credential or External Credential.
2. **Identify auth requirements** — determine whether the callout uses OAuth (Bearer token), Basic auth (username/password), or custom headers. For per-user OAuth, note which External Credential principal type is configured (`PerUserPrincipal`).
3. **Write the Apex callout** — use `callout:<NCApiName>/path` in `setEndpoint()`. Set an explicit timeout. Do not manually construct auth headers; rely on the Named Credential custom header formulas.
4. **Add a pre-callout auth gate if needed** — for per-user OAuth flows, query `UserExternalCredential` before the callout and return a graceful error or redirect if no record exists.
5. **Write unit tests with mock** — Named Credential callouts require `HttpCalloutMock` in test context. Verify status codes, mock response bodies, and test both authenticated and unauthenticated paths.
6. **Validate** — run `python3 check_apex_named_credentials_patterns.py --manifest-dir <project_root>` and confirm no hardcoded endpoint or credential anti-patterns are present.

---

## Review Checklist

- [ ] All callout endpoints use `callout:<NCApiName>` prefix — no hardcoded base URLs with credentials.
- [ ] No credentials (tokens, passwords) set via `req.setHeader()` directly in Apex code.
- [ ] `req.setTimeout()` is set explicitly; default 10-second timeout is almost always too short.
- [ ] Per-user OAuth flows check `UserExternalCredential` before attempting the callout.
- [ ] Tests use `HttpCalloutMock` and cover non-200 response codes.
- [ ] Named Credentials are not used with the Continuation framework.
- [ ] Formula tokens (`{!$Credential.OAuthToken}`) are only configured in Named Credential custom header fields, not in Apex string literals or request bodies.
- [ ] Managed package distribution scenarios account for Remote Site Settings in subscriber orgs.

---

## Salesforce-Specific Gotchas

1. **Named Credentials cannot be used with the Continuation framework** — the `callout:` prefix is incompatible with Visualforce/Aura Continuation async callouts. Attempting to use `callout:NCName` as the Continuation endpoint URL results in a runtime error. Use synchronous callouts from Queueable instead if async behavior is needed.
2. **Formula tokens only resolve in Named Credential custom header fields, not in Apex** — `{!$Credential.OAuthToken}` in an Apex string literal or request body is sent as the literal text `{!$Credential.OAuthToken}` to the external system. Developers expecting it to be replaced at runtime are surprised when the API rejects the malformed token.
3. **Remote Site Settings exemption does not automatically apply in managed packages** — when a Named Credential is in a managed package, the subscriber org does not automatically receive a Remote Site Settings entry for the endpoint. If any code path in the package makes a direct URL callout (bypassing the Named Credential), it will fail. Always use the `callout:` prefix consistently.
4. **UserExternalCredential is only available in the enhanced model** — querying this SObject in a legacy-model org throws a compile error or runtime exception. Guard with a feature check or document the org model requirement clearly.
5. **Per-user token expiry is not reflected in UserExternalCredential** — a `UserExternalCredential` record existing does not guarantee the token is valid; it only means the user completed the OAuth flow at some point. A 401 from the external API is the only signal that the token has expired and the user must re-authenticate.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Apex callout class | `Http().send()` call using `callout:NCName/path` with explicit timeout and no hardcoded auth |
| Pre-callout auth gate | SOQL-based `UserExternalCredential` check returning boolean auth status for current user |
| Named Credential review findings | List of anti-patterns found (hardcoded endpoints, inline tokens, missing timeout, Continuation misuse) |

---

## Related Skills

- `integration/named-credentials-setup` — use for creating or configuring Named Credentials and External Credentials in the Setup UI. This skill covers only the Apex consumption patterns.
- `integration/callouts-and-http-integrations` — use for general HTTP callout mechanics (HttpRequest, HttpResponse, mock patterns, error handling) that apply regardless of Named Credentials.
- `apex/apex-queueable-patterns` — use when Named Credential callouts need to run asynchronously outside a synchronous transaction.
- `apex/callout-limits-and-async-patterns` — use when hitting the 100 callout per transaction limit or designing retry/backoff for callout failures.

Related Skills

mfa-enforcement-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design MFA enforcement: auto-enablement, Salesforce Authenticator rollout, exceptions, service accounts, API-only users, SSO interop, and audit. Trigger keywords: MFA, multi-factor, two-factor, Salesforce Authenticator, MFA exception, MFA SSO, api-only MFA. Does NOT cover: end-user password policies, device-trust posture, or non-Salesforce IdP configuration.

encrypted-field-query-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design SOQL, filters, reporting, and indexes against Shield Platform Encryption fields. Trigger keywords: Shield Platform Encryption, encrypted field query, probabilistic vs deterministic encryption, encrypted SOQL filter, encrypted field index. Does NOT cover: Classic Encryption (deprecated), field-level security policy, or tenant secret key rotation.

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

omnistudio-testing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when testing or validating OmniStudio components — OmniScript preview, Integration Procedure step debugging, DataRaptor field-mapping validation, and end-to-end UTAM-based automation. NOT for Apex unit testing or standard Flow debugging.

omnistudio-error-handling-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing fault behavior across Integration Procedures, DataRaptors, OmniScripts, and FlexCards — error routing, user-facing messaging, retry semantics, and idempotency. Triggers: 'omnistudio error', 'integration procedure fault', 'dataraptor error handling', 'omniscript retry', 'flexcard action failure'. NOT for general Apex exception design or Flow fault paths.

omnistudio-ci-cd-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or implementing CI/CD pipelines for OmniStudio components — DataPack export/import, versioning, environment promotion, and automated deployment. NOT for standard Salesforce metadata CI/CD or Apex-only pipelines.

omniscript-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniScripts for guided experiences, step structure, branching, save/resume, and the boundary between OmniScript, Integration Procedures, DataRaptors, and custom LWCs. Triggers: 'omniscript design', 'too many steps in omniscript', 'save and resume omniscript', 'branching in omniscript', 'when should this be an integration procedure'. NOT for deep Integration Procedure or DataRaptor design when the guided interaction layer is not the main concern.

integration-procedure-cacheable-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers cache key design, TTL selection, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss/stale. Does NOT cover general IP authoring (see omnistudio-error-handling-patterns) or LWC client-side caching.

flexcard-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing, building, or reviewing OmniStudio FlexCards — including data source selection, card states, actions, conditional visibility, flyout configuration, and child card iteration. Triggers: 'FlexCard', 'card template', 'flyout', 'card action', 'card state', 'data source', 'child card', 'conditional visibility'. NOT for OmniScript design, standalone LWC development, or Apex controller architecture outside the FlexCard context.

dataraptor-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniStudio DataRaptors, especially Extract versus Turbo Extract versus Transform versus Load, field mapping strategy, performance tradeoffs, and when to move work into Integration Procedures or Apex. Triggers: 'DataRaptor Extract', 'Turbo Extract', 'DataRaptor Load', 'DataRaptor Transform', 'OmniStudio data mapping'. NOT for overall OmniScript journey design or Integration Procedure sequencing when the main question is not the DataRaptor shape itself.

wire-service-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.

message-channel-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when implementing Lightning Message Service (LMS) to enable cross-DOM communication between LWC, Aura, and Visualforce components on the same Lightning page, using message channels. Triggers: 'communicate between unrelated LWC components', 'send data between Visualforce and LWC', 'lightning message service not working', 'APPLICATION_SCOPE vs default scope', 'message channel metadata deployment'. NOT for parent-child component communication (use component-communication) or server-side events.