If you’ve ever juggled environment-specific config in Salesforce and cursed the tedium of copying values manually between orgs—good news: Custom Metadata Types (CMTs) are your new best friend.
Unlike Custom Settings (which many still cling to out of habit), Custom Metadata Types are deployable, cache-friendly, and accessible just about anywhere—from Apex to Flows to validation rules. This post walks through what they are, how to use them, and some killer use cases to help you get started.
What Are Custom Metadata Types?
Custom Metadata Types (also referred to as MDTs or CMTs) are like Custom Objects—but specifically for storing configuration data that needs to travel with your metadata across environments.
Think: business logic, thresholds, routing rules, feature toggles. The stuff you don’t want hardcoded in Apex or buried in admin-only setting pages.
Key Features:
- Deployable records and schema via change sets or metadata API.
- Referenceable in Apex, Flows, formulas, and validation rules.
- SOQL-free access in most contexts—fast and governor-friendly.
How Do Custom Metadata Types Work?
1. Define the Metadata Type
You start by creating the type—just like you’d create a Custom Object—but this one’s built for config. Add custom fields like Threshold__c, IsEnabled__c, or anything else your logic needs.
2. Add Records
Populate it with records. Each one is like a config scenario—“Healthcare Threshold,” “NA Routing Rule,” etc.
3. Use in Code and Config
Once created, these records can be used everywhere:
Apex Example:
MyMetadataType__mdt record = MyMetadataType__mdt.getInstance('RecordName');
System.debug('Threshold: ' + record.Threshold__c);
Formula Example:
$CustomMetadata.MyMetadataType__mdt.RecordName.FieldName__c
Yep, you can use them in validation rules and formulas, too.
Common Use Cases for Custom Metadata Types
1. Feature Toggles
Turn features on or off without touching code.
Fields:
- FeatureName__c (Text)
- IsEnabled__c (Checkbox)
Apex reference:
if (FeatureToggle__mdt.getInstance('NewFeature').IsEnabled__c) {
// Run the feature
}
2. Routing Records by Business Logic
Route leads, cases, or any object based on region, source, or vertical.
Fields:
- Region__c
- LeadSource__c
- OwnerId__c (Text—since CMTs don’t support lookups)
In Flows, you can query the right metadata record and grab the OwnerId__c to dynamically assign ownership.
3. Validation Rule Thresholds
Make thresholds dynamic without hardcoding.
Fields:
- Industry__c
- Threshold__c
- ValidationMessage__c
Example Validation Rule:
ISPICKVAL(StageName, 'Closed Won') &&
Amount >
CASE( Industry,
"Healthcare", $CustomMetadata.ValidationRules__mdt.Healthcare_Threshold.Threshold__c,
"Technology", $CustomMetadata.ValidationRules__mdt.Technology_Threshold.Threshold__c,
$CustomMetadata.ValidationRules__mdt.Default_Threshold.Threshold__c
)
Notice how you can directly reference the name of the record itself (e.g. Healthcare_Threshold) to traverse down to a specific field on that record. This is a really powerful feature of CMTs.
4. Dynamic Email Recipients
Here’s a cool one: route opportunity notifications to different recipients based on region, vertical, and deal size.
Example metadata table:
| Field Name | Type | Description |
|---|---|---|
| Region__c | Text | e.g. NA, EMEA |
| Vertical__c | Text | e.g. Healthcare, Tech |
| Threshold__c | Number | Min opportunity amount |
| EmailRecipients__c | Text | Comma-separated emails |
Example records:
| Region | Vertical | Threshold | Email Recipients |
|---|---|---|---|
| NA | Healthcare | 150,000 | john.doe@example.com, jane.doe@example.com, some.executive@example.com |
| NA | Healthcare | 50,000 | john.doe@example.com, jane.doe@example.com |
How you could reference this in Flow or Code:
The premise here is to regulate the end recipients of an email alert based on the values in a matching record based on a Custom Metadata reference. Think of this is a “reference table” where the data in the table controls some process (Flow or Apex). The Flow or Apex is independent of the data in the table, so changes to these configurations do not require changes to the Flow or Apex; only changes to the records in the table are necessary and the rest of the process will execute according to the logic in the process automatically.
In the example records above, notice that North American healthcare organizations will send an email to the 2 John/Jane Doe emails if the Opportunity’s ACV is over $50k, but for Opportunities that go over $150k ACV then there is an executive that also needs to receive the email.
Here’s a model you could follow in Flow to make this work:
- Trigger an Opportunity After-Save flow based on an Opp moving to Closed Won stage
- Query custom metadata records that match the attributes of the Opp
- If the Opp is in North America and is Healthcare and the ACV is over $150k, the 3 recipients will be returned and used in an email;
- Else if the Opp is in North America and is Healthcare and the ACV is over $50k, then the 2 recipients will be returned and used in an email;
- Else if the Opp is not North America, not Healthcare, or not over $50k ACV, then no record is returned (i.e. no email will send)
This is a simple example. In reality, you would have many records here covering key regions, industries, and ACV thresholds and indicating different email recipients as needed for each, but the principle still stands.
Note that the order of the records matters. The query against the Custom Metadata will run top-to-bottom by default and will return whichever record is the first match retrieved. To control this, it’s common to use an Index__c field to control the order by which the Flow or Apex will review the metadata reference table, and pick the first match based on that order. This is easy to do in Flow and Apex alike. In SOQL, this would work with a statement like “Order By Index__c ASC, Limit 1” but you can built this into Flow in a Get element just the same.
Benefits of Custom Metadata Types
- Deployable across orgs—no more manual record creation. Both data and metadata are deployable.
- Governor-limit friendly—no SOQL or CPU cost in most use cases.
- Highly reusable—ideal for logic that changes over time.
- Works with Flows, Apex, Validation Rules, Formulas—super versatile.
Limitations to Watch Out For
- No Lookup Fields—everything’s flat. If you want to store an ID to refer to a User, you have to enter their exact User ID into a text column. I recommend also using a Name column next to it so Admins can decode which User is referenced by name.
- Static Records—you can’t modify them at runtime via code or flow, and cannot create/edit/delete metadata records in any automations.
- Storage Limits—only 10MB across all CMTs in your org (but this is usually enough).
Best Practices
- Use clear naming for your records—makes debugging easier.
- Keep config centralized—don’t spread business rules all over the place.
- Document what each metadata type is for—future you (or teammates) will thank you.
Wrapping Up
Custom Metadata Types are one of the most underutilized but powerful tools in the Salesforce platform. They bring structure, reusability, and deployability to your org’s configuration—and when used well, they make your codebase lighter and more adaptable.
So the next time you’re tempted to hardcode a value or create a new custom setting, stop and ask yourself: “Can this be a Custom Metadata Type?” The answer might just be yes.