Bypass_Automation__c checkbox an admin flips during data load → hierarchy Custom Setting. The word 'Submit' translated to Hindi → Custom Label.
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:
- Create a Custom Metadata Type:
Fee_Rate__mdtwithCity__candRate__cfields. - Add one record per city. Apex reads them with zero SOQL-limit cost.
- The records are metadata — they DEPLOY with your package, so every sandbox carries the same table.
- 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.
๐ 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()orgetInstance(), and those reads do not count against SOQL limits. They support relationships and serve as 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 overrides.
- Hierarchy Custom Settings are the only ones you can use directly in formulas and validation rules via
$Setup. - Custom Labels are translatable text: Used specifically for UI strings.
Two smaller cousins belong here because both keep text out of code: A Global Value Set is a single picklist value list held once and shared across multiple picklist fields, updating every field when a value is added. Translation Workbench translates labels, picklist values, and error messages per user language. Custom Labels feed directly into Translation Workbench, which is the true 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 limits. Records travel cleanly inside packages and change sets.
Reach for: A Custom Label first if it is display text. A hierarchy Custom Setting when a value must differ per profile or per user at runtime. Custom Metadata when config must DEPLOY with the app, needs relationships, or is read in a loop (zero SOQL cost). A plain custom object only when business users must edit records using normal permissions and you accept the SOQL cost.
Price: Custom Metadata is metadata. Changing it in production requires the Customize Application permission. Updating it programmatically requires an async Metadata API deployment rather than DML.
Limits: Hierarchy Custom Settings resolve org → profile → user, cached with no SOQL cost. List Custom Settings do not deploy their data rows. Custom Labels are text-only, up to 5,000 per org.
Mirror — a plain custom object for config: Business users can edit it using normal permissions, but you pay a SOQL query on every read, and records do not deploy automatically with app releases.
Later: This choice determines how migration bypass switches are structured and what your packages can carry.
At volume: Reading configuration inside a loop is a classic limit killer. Read once and hold the result in a static variable.
Core Q&A
A: Default to Custom Metadata Types (CMT). It deploys with the app, eliminating post-deploy data loads. It is packageable, cached, and costs nothing against SOQL limits, making it ideal for feature flags, integration endpoints, business rules, and trigger-bypass switches.
- Choose a Hierarchy Custom Setting in two specific cases:
- When you need a PER-USER or PER-PROFILE override (CMT cannot do this).
- When you need to reference
$Setupin a formula or validation rule. - List Custom Settings: Rarely chosen now since CMT covers most list scenarios. Their remaining edge is that Apex can create and edit list custom settings synchronously at runtime via DML. CMT cannot do this synchronously because updates go through the async Metadata API.
Follow-ups (Scenario-Based)
A1: Use a Hierarchy Custom Setting with a checkbox field.
- The admin applies the override to the specific integration or migration user.
- Apex reads it efficiently with zero SOQL cost from the cache.
- Validation rules honor the exact same switch declaratively using
$Setup. - One mechanism gates both programmatic Apex and declarative rules cleanly.
- CMT cannot express a per-user override, and a custom object would require a SOQL query in every transaction.
A2: CMT records are metadata. Editing them directly requires the Customize Application system permission, which is too broad to grant business users. Furthermore, updating CMT records from Apex is an asynchronous metadata deployment, not synchronous DML.
- Option 1: Build a custom admin UI in front of the CMT that executes Metadata API calls under a service context.
- Option 2: Move business-editable rules to a custom object (real data with standard CRUD/FLS permissions) while keeping CMT for deployment-time configuration.
- Architect Note: Distinguishing 'application configuration' from 'business-managed data' is the core architectural decision.
Recommended Reading: Record Types & Page Layout Strategy