Custom Metadata Types vs Custom
Settings vs Custom Labels
💬 In plain
words: Three lookalikes:
Custom Metadata = configuration that DEPLOYS with your code (best for app
settings). Custom Settings = org/user-specific values, changeable at runtime
(hierarchy type is great for bypass switches). Custom Labels = translatable
text for the UI.
📌 Example: API endpoint URLs per environment → Custom
Metadata (deploys with code, sandbox vs prod values). A 'Bypass_Automation__c'
checkbox an admin flips during data load → hierarchy Custom Setting. The word
'Submit' translated to Hindi → Custom Label.
🎬 Real-Life
Example: The Fee Table Trapped Inside Code
Skyline
charges a different delivery fee per city. Apex needs those rates on every
booking.
The Old/Bad Way: if (city == 'Delhi') fee = 50. Else if (city ==
'Mumbai') fee = 65. … Every rate change is a code change, a test run, and a
deployment.
Why this is bad: Business data is trapped in code. Admins cannot
touch it. Sandboxes drift from production. A price change on Friday waits for a
release window.
The New/Good Way: 1. Create a Custom Metadata Type: Fee_Rate__mdt
with City__c and Rate__c fields. 2. Add one record per city. Apex reads them
with zero SOQL-limit cost. 3. The records are metadata — they DEPLOY with your
package, so every sandbox carries the same table. 4. Need per-user or per-org
runtime values instead? That is what a hierarchy Custom Setting is for.
The payoff: Configuration is data about the app. Store it
as metadata the admin can edit — never as if-else branches only a deploy can
change.
🧠Deploys,
changes, translates: Metadata DEPLOYS with
code. Settings CHANGE at runtime. Labels TRANSLATE text. Pick by which of the
three you need.
🔗
Connects to: migration bypass flags are built on
these · Custom Metadata ships inside packages; Settings do not
Concept
All three keep
configuration out of code.
• Custom Metadata Types (CMT) are METADATA.
• The records deploy in packages and change
sets.
• You read them with getAll or getInstance, and
those reads do not count against SOQL limits. They support relationships. This
is your default for app configuration and rule engines.
• Custom Settings are DATA. List settings are
org-wide.
• Hierarchy settings resolve org, then profile,
then user override.
• Hierarchy is the only one you can use
directly in formulas and validation rules, through $Setup.
• Custom Labels are translatable text for UI strings.
🧠360 Card —
Metadata vs Settings vs Labels
Two smaller
cousins belong here, because both keep text out of code. A Global Value Set is
one picklist value list held once and shared by many picklist fields. So adding
a value updates every field that uses it. Translation Workbench is the tool
that translates labels, picklist values and error messages per user language.
Custom Labels are what it usually translates. Which is the real reason to put
user-facing text in a label rather than hard-coding it.
Rule: Metadata deploys. Settings change at runtime.
Labels translate.
Gain: Custom Metadata reads are free. getAll and
getInstance cost no SOQL. The records travel inside packages and change sets.
Reach for: a Custom Label first, if the thing is display
text. A hierarchy Custom Setting when a value must differ per profile or per
user at runtime. Custom Metadata when the config must DEPLOY with the app, or
needs relationships, or is read in a loop — its reads cost no SOQL. A plain
custom object only when business users must edit it with normal record
permissions and you accept the SOQL cost.
Price: Custom Metadata is metadata. Changing it in
production needs Customize Application. Updating it from code is an async
deployment, not DML.
Limits: hierarchy Custom Settings resolve org, then
profile, then user, cached, with no SOQL cost. List Custom Settings do not
deploy their data. Custom Labels are text only, up to 5,000 per org.
Mirror — a plain custom
object for config: business users can edit
it with normal permissions. But you pay SOQL on every read. And it does not
deploy with the app.
Later: this choice decides your migration bypass
switches and what your package can carry (11.2, 12.3).
At volume: reading config inside a loop is the classic
limit killer. Read once, hold it in a static.
Core Q&A
Q: When do you choose Custom
Metadata over a Custom Setting, and when is it the other way around?
🎯
Say this first: Custom Metadata for config that deploys
with code and differs per environment. Custom Settings (hierarchy) for values
you change at runtime, per user or profile — like bypass flags.
A: Default
to CMT. It deploys with the app, so there is no post-deploy data load.
It is packageable, it is cached, and it costs nothing against SOQL
limits. That makes it ideal for feature flags, integration endpoints, rules and
thresholds, and trigger-bypass switches.
• Choose a Hierarchy Custom Setting in two
cases.
• When you need a PER-USER or PER-PROFILE
override, because CMT cannot do that.
• Or when you need $Setup in a formula or a validation
rule.
• Choose a List Custom Setting rarely — CMT has
taken over most of what it did. Its one remaining edge: Apex can create and
edit its records at runtime.
• CMT cannot do that synchronously, because CMT
writes go through the async Metadata deployment API.
Follow-ups (scenario-based)
Q1: Your trigger framework
needs a 'bypass automation for this user during data load' switch. Which
mechanism and why?
A1: Use
a Hierarchy Custom Setting with a checkbox.
• The admin sets the override on the specific
integration or migration user.
• Code reads it cheaply — it is cached, and it
costs no SOQL.
• Validation rules honour the same switch
through $Setup.
• One mechanism gates both the programmatic and
the declarative side.
• CMT cannot express a per-user override.
• A custom object would cost you a query in
every transaction.
Q2: A rule engine stores
dozens of business rules as CMT records. What breaks when business users ask to
edit rules themselves in production?
A2: CMT
records are metadata. Editing them needs the Customize Application permission,
which is far too broad to hand a business user. And updating them from code is
an asynchronous deployment, not DML.
• You have two ways out. Put a controlled admin
app in front of the CMT and let it call the Metadata API under a service
context.
• Or move the genuinely business-editable rules
to a custom object — real data, with proper Create, Read, Update, Delete
(CRUD) and Field-Level Security (FLS) — and keep CMT for deploy-time
configuration.