Salesforce

Custom Metadata Types vs Custom Settings in Salesforce

Custom metadata types win almost every argument against custom settings. They deploy like real metadata, they don't touch your query limits, and they support relationships to other objects. Custom settings still have one job they do better: storing data that changes per user or per profile without a deployment. Everything else, use custom metadata.

That one-line answer is the article most people need. The rest is about why that rule holds up, and the handful of cases where it doesn't.

What each one actually is

Custom settings came first. They're a special kind of object, either List or Hierarchy, that lives outside your normal record storage and gets cached for fast access. You edit the values through Setup, or through Apex, and they stay put no matter how many records exist in the org.

Custom metadata types showed up later and solved a specific problem: configuration that needs to move between sandboxes and production like real code. A custom metadata record behaves like metadata, not data. You can include it in a change set. You can package it. You can deploy it with a CI pipeline the same way you deploy an Apex class.

Both store key-value style configuration. Both get referenced constantly in Apex and Flow. The difference is what happens to that configuration once it leaves your sandbox.

The deployment problem custom settings never solved

Here's the failure mode every admin has hit. You configure a custom setting in your dev sandbox, test the feature, everything works. You deploy the Apex and the object changes. The custom setting values stay behind, because custom setting data isn't metadata, it's data. Someone has to manually re-enter every value in every environment, every time.

Custom metadata types fixed this on day one. A custom metadata record deploys with the rest of your changes. No manual re-entry, no forgotten row in production that breaks a trigger three weeks later. If your org promotes changes through multiple sandboxes before production, this alone should end the debate.

Salesforce built custom metadata specifically to be treated as metadata. That's not a marketing distinction. It changes how the record behaves in every deployment tool you already use: change sets, Metadata API, SFDX, Copado, Gearset, all of it.

Governor limits: the quiet reason custom metadata wins twice

Custom settings, when accessed via Apex, still count against your SOQL query limits if you query them directly instead of using the cached getInstance methods correctly. Developers get this wrong constantly. They call a direct SOQL query on a custom setting object inside a loop, and now they're burning limits on configuration data that never should have touched the governor limits in the first place.

Custom metadata records don't have this problem at all. Reading them through Apex, or through the built-in getAll() and getInstance() methods, never counts against your SOQL limit. Ever. That's a hard rule, not a best practice. If you're storing feature flags, API endpoints, threshold values, or any configuration read repeatedly inside bulk Apex, custom metadata is the only sane choice.

List custom settings do get cached in a similar way and largely avoid this issue too, if you use the correct access pattern. But the moment a junior developer queries them wrong, you've got a limit problem hiding in production code that passed every test in a sandbox with ten records.

Where custom settings still earn their place

Hierarchy custom settings do one thing custom metadata cannot replicate: they resolve values differently per user, per profile, or per org, automatically, with zero code to handle the fallback logic. Set an org-wide default, override it for a profile, override that for a specific user, and Apex just calls getInstance() and gets the right answer for whoever's running the code.

That's genuinely useful for things like per-user API keys, feature toggles that only certain profiles should see, or throttling rules that differ by role. Custom metadata types have no equivalent hierarchy resolution. You'd have to build that logic yourself in Apex, checking the running user's profile or permission set and branching accordingly. It's not hard, but it's code you have to write, test, and maintain forever.

List custom settings, the non-hierarchy kind, have mostly lost their reason to exist. Anything a list custom setting can do, a custom metadata type does the same way, plus deployability. There's no strong case left for choosing a list custom setting over custom metadata in a new build.

Relationships, validation, and the stuff custom settings can't touch

Custom metadata types support lookup relationships to other custom metadata types and to standard objects like RecordType. They support validation rules. They support field-level security in the same way custom objects do. You can build a genuinely relational configuration layer: a custom metadata type for regions, another for tax rules, linked together, all deployable as one unit.

Custom settings support none of this in any real depth. They're flat. A field on a custom setting can't look up to another custom metadata type at all, and the validation options are thin. If your configuration has any structure to it beyond a flat key-value pair, custom metadata is doing a job custom settings were never built for.

This matters more as orgs mature. A startup's custom settings usage looks like five flags and a discount percentage. A five-year-old org's configuration looks like a small relational database of business rules, and that only works cleanly with custom metadata types underneath it.

The decision rule, stated plainly

Default to custom metadata types. Every time. They deploy cleanly, they never touch SOQL limits when read correctly, and they support real relationships and validation. There's no meaningful downside for standard configuration storage in a modern org.

Reach for hierarchy custom settings only when you need automatic per-user or per-profile value resolution and don't want to write that branching logic yourself. That's a narrow, legitimate use case, not a default. Everything else, including anything tagged as a "list custom setting" in an older org, should get migrated to custom metadata the next time you touch that code.

If you inherited an org full of list custom settings, that's technical debt, not a design choice. Migrating them is usually a half-day job per object: create the custom metadata type, port the records, repoint the Apex references. It pays for itself the first time someone tries to deploy a config change without manually re-entering it in production.

Frequently Asked Questions

Do custom metadata types count against SOQL query limits?

No. Reading custom metadata records through Apex, including direct SOQL against them, does not count against your governor limit for SOQL queries. This is one of the main reasons they're preferred over custom settings for configuration read inside heavy Apex logic.

Can custom metadata types be deployed through change sets?

Yes, custom metadata types and their records deploy through change sets, Metadata API, and SFDX the same way Apex classes and objects do. This is the core advantage over custom settings, whose data does not move automatically between environments.

When should I still use a hierarchy custom setting instead of custom metadata?

Use a hierarchy custom setting when you need Salesforce to automatically resolve a different value per user, profile, or org without writing branching logic yourself. Custom metadata types have no built-in hierarchy resolution, so replicating that behavior requires custom Apex.

Are list custom settings still worth using in new development?

Generally no. Custom metadata types replicate everything a list custom setting does while adding deployability, relationships, and validation rules. Most Salesforce architects treat list custom settings as legacy at this point.

Can custom metadata types have relationships to other objects?

Yes, custom metadata types support lookup-style relationships to other custom metadata types and to certain standard objects like RecordType. Custom settings do not support this, since their fields stay flat with no relational structure.