If you’ve been in Salesforce integration work long enough, you’ve seen this incident: a team deploys a fresh integration to production. The Apex class deploys. The flow deploys. The external service endpoint configuration, stored as a Custom Setting, does not — because Custom Settings are data, not metadata.
Half an hour later, the team is scrambling to manually re-create records in production, against a clock, while integrations are either silently failing or retrying against the wrong endpoint. I’ve watched this happen three times in three different orgs.
The fix has been sitting in the platform for a decade. Custom Metadata Types. Yet integration teams still reach for Custom Settings first — mostly, I think, because nobody ever gave them a focused playbook on what to use when.
Here it is.
The One Rule That Changes Everything
Custom Metadata Types deploy with your code. Custom Settings records do not. Everything downstream follows from that single fact.
Custom Settings are a data-model feature — the records you create live in your org’s data storage and are not part of your metadata deployment. Custom Metadata Types, per Salesforce’s Custom Metadata Types documentation, are fully metadata — both the type and every record travel in Metadata API deployments, change sets, and packages.
That single difference is the reason integration configuration should live in CMT.
What You Should Store in CMT for an Integration
An integration has three kinds of configuration. Each belongs somewhere different:
- Endpoints, mappings, feature flags, thresholds → Custom Metadata Types
- Secrets (API keys, OAuth credentials, passwords) → Named Credentials
- Per-user or per-profile runtime overrides → Custom Settings (Hierarchy)
The single biggest mistake I see is reaching for Custom Settings when the thing being configured has nothing to do with a specific user or profile. API endpoints are not user-specific. Feature flags that control an integration’s behaviour globally are not user-specific. They belong in metadata, not data.
The Governor-Limit Advantage
Custom Metadata Type records are not data rows in the traditional sense. Per Salesforce’s CMT Allocations and Usage Calculations doc:
- CMT reads are cached and do not consume SOQL query row or query count governor limits
- CMT counts against configuration data limits, not data storage limits
- Records are automatically available in Apex, Flow, and Visualforce without manual load patterns
In practice, this means you can put a hundred mapping rules in a CMT and read them on every trigger without ever worrying about the 100-SOQL-query-per-transaction limit. That’s a structural win for integrations that run at trigger context.
For field mappings between your Salesforce object and an external system, build a CMT with fields like Source_Field__c, Target_Field__c, Transformation__c. Your integration loop reads the collection once, uses the map, and never touches a SOQL query for config. Adding a new field becomes a deploy, not an ops ticket.
The Limits You Need to Respect
The official Custom Metadata Types Limitations documentation is worth bookmarking. The ones that bite integration teams:
| Limit | Value | Why it matters |
|---|---|---|
| Custom Metadata Types per org | 200 | Shared across installed packages — watch this in managed-package-heavy orgs |
| Custom fields per type | 100 | Design tables with normalized fields, not one big blob |
| Record size | 500 KB per record (varies) | Not a table for large free-form content |
| Deployment | Metadata API / change sets / unlocked packages | No surprise — metadata behaves like metadata |
The 200-type limit is the sneaky one. If you’re in an org that ships with three or four managed packages and each consumes 20-40 CMT slots, you can exhaust the budget faster than you expect.
Setup → Company Information → System Overview shows your current CMT usage. Check this before rolling out a pattern that uses dozens of types. Easier to consolidate early than re-architect at 180/200.
Named Credentials — The Secrets Pair
CMT is not encrypted, full stop. Do not put API keys, OAuth client secrets, or passwords in it. Salesforce ships Named Credentials specifically for this purpose — credentials are encrypted at rest, the platform manages token refresh, and the integration Apex or Flow references the credential by name, not by value.
The pattern that works cleanly:
- The endpoint’s non-secret configuration (base URL, timeout, retry count, feature flags) lives in CMT
- The endpoint’s secret configuration (auth header values, tokens) lives in the Named Credential
- The integration code resolves both at call time
This gives you the deployability of CMT for the everyday config, and the security of Named Credentials for the sensitive bits.
A Migration Plan for Teams Still on Custom Settings
The four-step migration I recommend
Step 1 — Inventory. List every Custom Setting in the org. For each, classify: does it change per user/profile (keep in Custom Settings), or is it a global config value (migrate to CMT)?
Step 2 — Design. For the global ones, design the CMT structure. Don’t just mirror the Custom Setting one-to-one — you often discover normalization opportunities (one mapping type instead of five sibling settings).
Step 3 — Dual-write during migration. For the transition period, have your Apex read CMT first, fall back to Custom Settings if not found. This lets you deploy the CMT, populate it with a data-loader / one-time script, and cut over without a hard switchover.
Step 4 — Remove the Custom Setting. Once traffic is fully on CMT in production for a release cycle, remove the fallback code, delete the Custom Setting. Stop paying for the operational overhead of keeping two sources in sync.
Problem: An integration team stored their SFTP host, port, timeout, and retry-count values in a Custom Setting. Every production deploy, they had a runbook entry: “remember to update XYZ_Config Custom Setting after deploy.” Twice in eighteen months, somebody forgot. Each time, the integration pointed at the sandbox SFTP until someone noticed.
Fix: A single CMT Integration_Endpoint__mdt with Host__c, Port__c, Timeout_Seconds__c, Retry_Count__c. Deployments now move the config along with the code. The runbook entry disappeared, and so did the incident class.
What to Leave in Custom Settings
Not everything should move. Hierarchy Custom Settings are genuinely the right tool when:
- A value needs to be overridden per user or per profile without a deployment
- An ops team needs to tune a threshold at runtime without involving developers
- A feature toggle needs to be flipped quickly in production without a package upgrade
The test is: “does this value change based on who is using the system, or does it need to change faster than our release cycle?” If yes, keep in Custom Settings. If no, it belongs in CMT.
What Still Confuses Teams
Three sneakier distinctions
CMT isn’t free in change sets. Adding a new CMT type to a change set adds the type definition. Adding a record requires also adding it as a separate component in the change set. If you use unlocked packages or Metadata API deployments, it’s cleaner.
CMT records are read-only in Apex. You cannot DML-insert or update CMT records from Apex. Changes go through the Apex Metadata API or Metadata API deployments. If your “config” needs to be modifiable at runtime by end users, that’s data, not metadata — reconsider whether CMT is the right fit.
Big CMT tables are not a substitute for a custom object. If you have hundreds of rows and the data changes monthly, you want a custom object. CMT is for configuration, not reference data that churns.
Where the Official Docs Live
- Custom Metadata Types Limitations — authoritative list of what you can and can’t do
- Custom Metadata Allocations and Usage Calculations — the sizing guide
- Named Credentials — for the secrets half of the pair
Read the limitations doc first. It’s the one that prevents design choices you’ll later regret.
What Custom Setting is currently embarrassing your integration team the most — and why hasn’t it moved yet?
How did this article make you feel?
Comments
Salesforce Tip