External Client Apps as source — the ExternalClientApplication and ExtlClntApp* metadata files, what goes in source and what stays in Setup, retrieve and deploy
How to keep an External Client App in a Salesforce DX project — the three files Salesforce's own CLI generator writes (ExternalClientApplication .eca, ExtlClntAppGlobalOauthSettings .ecaGlblOauth with the callback URL, ExtlClntAppOauthSettings .ecaOauth with the scopes), the policy and flow settings types, why consumer keys and secrets never belong in source, and retrieving an app configured in Setup so its policies are captured exactly.
markdownreferencev13TMQ-HWBBstage: deploytrust: officialservice: authinterface: iacinterface: cliExternalClientApplication metadata.eca-meta.xmlexternalClientApps folderExtlClntAppGlobalOauthSettings.ecaGlblOauth-meta.xmlextlClntAppGlobalOauthSetscallbackUrlisConsumerSecretOptionalExtlClntAppOauthSettings.ecaOauth-meta.xmlcommaSeparatedOauthScopesExtlClntAppOauthConfigurablePolicies.ecaOauthPlcyExtlClntAppConfigurablePoliciesdistributionState LocalPackagedECA source formatsf project retrieve start --metadata ExternalClientApplicationconsumer key not in metadataLightning Out 2.0 ECA
External Client Apps as source
An ECA is several metadata components that point at one another by name. This is the set Salesforce's CLI generator writes, for an ECA that signs users in with the authorization code flow and PKCE and needs no client secret:
<!-- externalClientApps/Agent_Chat_Web.eca-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ExternalClientApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<contactEmail>platform-team@<example></contactEmail>
<description>Web chat front end (OAuth auth code + PKCE, no client secret).</description>
<distributionState>Local</distributionState>
<label>Agent_Chat_Web</label>
</ExternalClientApplication>
<!-- extlClntAppGlobalOauthSets/Agent_Chat_Web.ecaGlblOauth-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ExtlClntAppGlobalOauthSettings xmlns="http://soap.sforce.com/2006/04/metadata">
<callbackUrl>https://chat.example.com/oauth/callback</callbackUrl>
<externalClientApplication>Agent_Chat_Web</externalClientApplication>
<isConsumerSecretOptional>true</isConsumerSecretOptional>
<label>Agent_Chat_Web Global OAuth</label>
</ExtlClntAppGlobalOauthSettings>
<!-- extlClntAppOauthSettings/Agent_Chat_Web.ecaOauth-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ExtlClntAppOauthSettings xmlns="http://soap.sforce.com/2006/04/metadata">
<externalClientApplication>Agent_Chat_Web</externalClientApplication>
<label>Agent_Chat_Web OAuth Settings</label>
<commaSeparatedOauthScopes>Web</commaSeparatedOauthScopes>
</ExtlClntAppOauthSettings>
| File (folder, suffix) | Holds |
|---|---|
ExternalClientApplication (externalClientApps, .eca) | Name, contact, description, distributionState (Local for an app used only in this org) |
ExtlClntAppGlobalOauthSettings (extlClntAppGlobalOauthSets, .ecaGlblOauth) | Callback URL, whether a consumer secret is optional |
ExtlClntAppOauthSettings (extlClntAppOauthSettings, .ecaOauth) | Scopes, comma-separated, as scope names (the generator uses Web) |
ExtlClntAppOauthConfigurablePolicies (extlClntAppOauthPolicies, .ecaOauthPlcy) and ExtlClntAppConfigurablePolicies (extlClntAppPolicies, .ecaPlcy) | Admin policies: permitted users, Run As user, IP relaxation, refresh token and session policies |
What not to put in source
- The consumer key and secret. They're generated by the org, read once in Setup (Settings → OAuth Settings → Consumer Key and Secret, after an email code) and kept in your secret store. Every org, including each sandbox and scratch org, has its own.
- Guessed field names. Salesforce's generator only shows the fields above. For the client credentials flow, JWT bearer, a Run As user or the Agent API scopes, configure the app once in Setup (
reference/auth/external-client-apps-and-oauth-flows.md), then retrieve it, so the policy files hold exactly what the org wrote:
sf project retrieve start --metadata ExternalClientApplication:Orders_Integration --metadata ExtlClntAppOauthSettings:Orders_Integration --metadata ExtlClntAppOauthConfigurablePolicies:Orders_Integration --target-org dev
The hub's reading: name every component after the app, as the generator does. A retrieve that returns differently named child components shows the names to use. Deploying the files to another org creates a new app with new keys there.