Custom Metadata Types store config as metadata — their records deploy with change sets and packages and can be referenced declaratively and in Apex. Custom Settings store config as data: records don’t deploy, but the Hierarchy type gives per-profile and per-user overrides at runtime, which Custom Metadata Types can’t. Choose by whether the config must travel or must vary by user.
Custom Metadata Types vs Custom Settings: Metadata or Data?
Both features exist for the same reason: to keep configuration out of hard-coded values and let admins tune app behavior without editing code. The decisive difference is what kind of thing the records are.
Metadata describes how an org is built — the structure of objects, fields, and rules. Data is the content stored inside that structure. A Custom Metadata Type (API suffix __mdt) is unusual because its records are metadata, not data. A Custom Setting (API suffix __c) behaves like a lightweight custom object whose rows are ordinary data.
That single classification — metadata versus data — drives almost every practical difference below: how each moves between orgs, how you edit it, how you read it in Apex, and whether it shows up in test classes.
The Deployment Line That Settles Most Decisions
When you deploy an app that uses Custom Metadata Types, the type, its fields, and its records all travel together in the change set or package. Nothing extra is needed on the destination org.
Custom Settings work differently. Only the definition — the setting and its fields — is included in a package or deployment. The records are left behind, so a subscriber or downstream org installs an empty setting and must populate the values afterward with an Apex or API script. Salesforce states this plainly: packages include custom setting definitions, not data.
This is why Custom Metadata Types have largely replaced List Custom Settings for new development. Mapping tables, feature switches, integration endpoints, and business rules all belong to the release, so having the values deploy with the metadata removes a whole class of “works in sandbox, empty in production” bugs.
Custom Metadata Type records deploy with change sets and packages. Custom Setting records do not — only the definition deploys, and data must be loaded post-install. This one distinction is the most common “which should I use” trap.
Where Each One Still Wins
Custom Metadata Types aren’t strictly better — they win on packaging and richness, while Custom Settings hold one capability their successor never gained.
Custom Metadata Types support relationship fields (a Metadata Relationship lookup to another type), are readable in Apex with both SOQL and cache methods, and appear in test classes without SeeAllData. Custom Settings support none of those relationships and their data is invisible in tests unless you create it in the test or set SeeAllData=true.
The one thing Custom Settings still do that Custom Metadata Types cannot is the Hierarchy type. A hierarchy custom setting resolves a value based on the running context — an org-wide default that a profile can override, which a specific user can override again. The most specific value wins. That per-user, per-profile runtime behavior has no built-in equivalent in Custom Metadata Types.
| Attribute | Custom Metadata Types (__mdt) | Custom Settings (__c) |
|---|---|---|
| Records are… | Metadata (config) | Data (rows, like a custom object) |
| Deploy with change sets & packages | Yes — records travel with the type | No — only the definition; data loaded after install |
| Per-profile / per-user runtime overrides | No built-in hierarchy | Hierarchy type: org → profile → user (most specific wins) |
| Relationship fields | Yes — Metadata Relationship lookups | No relationship fields |
| Declarative reference | $CustomMetadata in validation rules & formulas; Get Records in Flow | $Setup (hierarchy only; list can’t be used in formulas/validation rules) |
| Apex reads | SOQL (doesn’t count against the SOQL query limit) plus getAll() / getInstance() | Cache methods getAll() / getInstance() / getValues(); SOQL bypasses the cache and counts |
| Apex writes | Create/update via async Apex Metadata API — no delete, no standard DML | Full DML — insert, update, delete like data |
| Visible in tests | Yes, without SeeAllData | No — needs data created in the test or SeeAllData=true |
Choosing Between Custom Metadata Types and Custom Settings
Start with one question: does the configuration belong to the release, or to the running user?
If the values are part of how the app works — mappings, thresholds, toggles, endpoints — they should ship with your metadata, so reach for a Custom Metadata Type. Both features can be read without spending a SOQL query when you use their cache methods, and both can be referenced declaratively: Custom Metadata Types through $CustomMetadata in validation rules and formulas (and Get Records in Flow), hierarchy Custom Settings through $Setup. This is the same declarative surface you already use when a formula field pulls in configuration data rather than a literal value.
If instead the value has to differ per user or profile at runtime — a per-rep commission rate, a user-level UI default — that’s the hierarchy Custom Setting’s home turf, and the cleaner choice. For the full picture of __mdt field types, visibility, limits, and the Apex Metadata API, see our complete guide to Custom Metadata Types in Salesforce, the pillar this brief sits under.
Default to Custom Metadata Types for deployable configuration; reach for a Hierarchy Custom Setting only when a value must vary by the running user. And don’t store true secrets in either outside a managed package — public and unmanaged-package records are readable by all profiles, including the guest user. Use named credentials or encrypted fields for tokens and passwords.
Exam-Ready Recap
- Metadata vs data —
__mdtrecords are metadata and deploy with the type;__ccustom setting records are data and are left behind on deployment. - Hierarchy is the differentiator — only Custom Settings offer org → profile → user resolution; Custom Metadata Types have no built-in hierarchy.
- List custom settings can’t be used in formulas or validation rules — only the hierarchy type is reachable via
$Setup; both types are reachable in Apex. - Test visibility — Custom Metadata Type records are visible in tests without
SeeAllData; custom setting data is not.
Verified against the official Salesforce Winter ’27 documentation, including Trailhead’s Custom Metadata Types module, the Apex Developer Guide on Custom Settings, and the Salesforce Help note that packages include custom setting definitions, not data. Study smarter at CertifySF.com.
