Every org accumulates configuration that isn’t quite data and isn’t quite code: currency-to-region mappings, tiered discount thresholds, per-country tax rules, feature toggles that turn behavior on and off. Hard-code those values in Apex and you ship a code change every time the business shifts. Store them in a custom object and you can maintain them easily — but the records get left behind when you deploy to production.
Custom metadata types in Salesforce solve exactly this problem. They let admins and developers define their own configuration and application metadata, populate it with records, and then deploy those records right alongside the type — no post-install data load, no migration script. Because the records are treated as metadata rather than data, they move through change sets and packages like any other component.
This guide walks through what custom metadata types are, the fields they support, how you read and write them from Apex, how they show up in declarative tools, their limits, and the practical patterns that make them one of the most useful configuration tools on the platform.
What Custom Metadata Types Are (and Why They Matter)
Start with the word itself. Metadata is data that describes other data. In a Salesforce org, the Account object’s structure — its fields, their names, their types — is metadata. The values you type into an account record (“Acme”, “San Francisco”) are data.
A custom metadata type is an object used to define the structure of application metadata. Here’s the twist that makes it special: the records of a custom metadata type are themselves metadata, not data. When you create a custom metadata record, you’re authoring a piece of configuration — the same category of thing as a validation rule or a page layout — that can be version-controlled, packaged, and deployed.
You’ll recognize a custom metadata type by its API suffix: it ends in __mdt instead of the __c you see on custom objects. Its custom fields still use __c, and its records don’t take a suffix at all.
When you deploy an app that uses custom metadata types, all of the records and fields ship with the type — no additional steps. Deploy a custom object or a custom setting instead, and only the definition (the “header”) travels; the records get left behind for you to load separately.
Where custom metadata types fit
Think of custom metadata types as configuration you build reusable functionality around. You define the type, populate records that describe how something should behave, and then your Apex, flows, formulas, or validation rules read those records and act accordingly. Change the behavior later by editing a record — not by editing code. It’s the same instinct behind record types in Salesforce: capture business configuration declaratively so behavior can change without a rebuild.
Custom Metadata Types vs Custom Settings vs Custom Objects
Three constructs can hold configuration, and knowing which to reach for is a heavily tested distinction. Custom objects store transactional data. Custom settings and custom metadata types both store configuration — but they behave very differently at deploy time and in Apex.
Custom Metadata Type Recommended
MyConfig__mdt- Records are metadata — they deploy with change sets, packages, and the Metadata API
- Supports relationship fields (Metadata Relationship)
- Visible in Apex tests without
SeeAllData - Apex can create, read, and update records — but not delete
- SOQL doesn’t count against the query governor limit
- One flat type (no list/hierarchy variants)
Custom Setting Legacy config
MyConfig__c- Records are data — they do not deploy via package, change set, or Metadata API
- No relationship fields
- Not visible in tests unless created or
SeeAllDatais used - Full CRUD in Apex, including delete, via standard DML
- Cached; SOQL bypasses the cache
- List or Hierarchy (per-user / per-profile / org defaults)
| Attribute | Custom Metadata Type | Custom Setting |
|---|---|---|
| API suffix | __mdt | __c |
| Records are… | Metadata | Data |
| Records deploy with the type? | Yes (change set / package / Metadata API) | No — migrate separately |
| Relationship fields | Yes — Metadata Relationship | No |
| Apex CRUD | Create, Read, Update (no Delete) | Full CRUD via DML |
Visible in tests without SeeAllData | Yes | No |
| SOQL vs query limit | Doesn’t count (unlimited) | Counts |
| Sub-types | Single type | List or Hierarchy |
For configuration you want to version, package, and promote through environments, reach for a custom metadata type first. Hierarchy custom settings still earn their place when you genuinely need per-user or per-profile overrides at runtime — a behavior custom metadata types don’t provide natively.
The Field Types You Can Use
A custom metadata type carries custom fields much like a custom object does. As of the Summer ’26 release, the supported field types are:
- Metadata Relationship — relate one custom metadata type to another, or to an entity definition / field definition
- Checkbox, Date, Date and Time, Email, Number, Percent, Phone, Picklist, Text, Text Area, Text Area (Long), and URL
The standout is the Metadata Relationship field. A master-detail relationship isn’t an option on a custom metadata type, but a Metadata Relationship field lets you build lookups between types — unlocking mapping tables and layered configuration that a lone flat type can’t express.
Three things trip people up. First, you can’t change a field’s type after it’s defined — a Text field can’t later become a Text Area. Second, custom metadata types don’t support Shield Platform Encryption on their fields. Third, there is no Formula field type on a custom metadata type — you can reference custom metadata records inside formula fields on other objects, but you can’t put a formula field on the type itself.
Creating a Custom Metadata Type and Its Records
Everything is declarative. From Setup, in the Quick Find box, enter Custom Metadata Types and follow the flow:
- On the All Custom Metadata Types page, click New Custom Metadata Type. Give it a Label, Plural Label, and Object Name (the API name that becomes
YourType__mdt). - Choose a Visibility: Public (accessible via Apex and the API broadly) or Protected (in a managed package, only code in the same namespace can see it).
- Under Custom Fields, click New to add fields. For each field, set a Field Manageability value that controls who can change it later.
- Click Manage Records to add the configuration rows themselves — each record gets a Label and a unique DeveloperName within the type.
Records can also be added, edited, or deleted through Setup or the Metadata API. Custom metadata types are available in Enterprise, Performance, Unlimited, and Developer editions; Professional and Group edition orgs can create, edit, and delete custom metadata records only from types delivered in installed packages.
Subscribers can’t add records to an installed custom metadata type that’s protected. To let subscribers create their own records against your type, the type must be public. Expect a scenario question that hinges on this distinction in a managed-package context.
Reading Records in Apex
There are two ways to read custom metadata records in Apex, and the difference matters for performance and for the exam.
Option 1 — SOQL (unlimited queries)
You can query a custom metadata type with ordinary SOQL. The headline benefit: these queries don’t count against the total number of SOQL queries allowed in an Apex transaction. You can issue effectively unlimited queries against custom metadata in a single transaction.
The one caveat: a query that selects a long text area field is the exception — those queries do count toward Apex governor limits.
Option 2 — getInstance / getAll (skip SOQL entirely)
Introduced in Spring ’21, static methods mirror the ones on custom settings and read straight from the application cache — no SOQL engine, no query-rows cost:
The available methods are getAll(), getInstance(recordId), getInstance(qualifiedApiName), and getInstance(developerName). getAll() returns a Map whose keys are the records’ DeveloperNames and whose values are the record sObjects. A nice bonus: because you aren’t naming specific fields, these methods pick up field changes automatically. Prefer them unless you need query-time filtering or you’re reading long text area fields.
Custom metadata records are visible in Apex tests by default — no @isTest(SeeAllData=true) required — precisely because they’re metadata. That’s a real advantage over custom settings and regular data, which tests can’t see unless you create them.
Creating and Updating Records from Apex
Here’s the sharpest edge in the whole topic. You cannot insert or update a custom metadata record with standard DML. Try insert myRecord; and it won’t work — DML operations aren’t allowed on custom metadata in Apex or in the Enterprise and Partner APIs.
Instead, Apex creates and updates custom metadata records through the Apex Metadata API: you build a deployment container and enqueue it for asynchronous processing. Apex can create and update custom metadata components this way — but not delete them.
The deployment runs asynchronously. Because it’s async, you provide a callback class that implements Metadata.DeployCallback; Salesforce invokes its handleResult() method once the deployment finishes. This is why runtime “edit your own config” features built on custom metadata always feel slightly deferred — the write is a queued metadata deployment, not an instantaneous DML commit.
Using Custom Metadata in Formulas, Validation Rules, and Flows
Custom metadata isn’t only for developers. Its whole point is to be consumed across the platform — most of it declaratively.
Formula fields and validation rules
You can reference a custom metadata record inside a formula using the $CustomMetadata global, with this syntax:
Use __mdt for the type and __c for the field; the record name takes no suffix. This is a clean way to centralize a value — a threshold, a rate, an endpoint — that many formulas share, so a single record edit updates them all at once. It pairs naturally with everything covered in our guide to formula fields in Salesforce. Note there’s no field-picker UI for the default-value case, so you type the $CustomMetadata path by hand there.
Flows
Flows read custom metadata with a Get Records element pointed at the __mdt object — ideal for driving decisions or assignments from configuration instead of hard-coded values. One nuance: SOQL that a flow issues against custom metadata does count toward the per-transaction limits Apex enforces, unlike the “unlimited” allowance you get querying directly in Apex.
Limits and Governance
Custom metadata is generous but not infinite. These are the current allocations to design around:
| Limit | Value |
|---|---|
| Custom metadata types per org | 200 (across every source — built, unmanaged, and managed) |
| Fields per custom metadata type or record | 100 |
| Custom metadata per org | 10 million characters |
| SOQL queries per Apex transaction | Unlimited (for custom metadata) |
| SOQL queries selecting long text area fields | Count toward Apex governor limits |
| Records returned per transaction | 50,000 |
| Characters per description field | 1,000 |
The 10-million-character org allocation is computed from the maximum field size of each field type, not the bytes you actually store — so a Text field sized to 9 for a US Social Security number consumes far less budget than a 255-character Text Area holding the same value. Size fields to your data, not to the maximum, to stay comfortably within the cap. Long text area fields count 255 characters each against that allocation. You can watch usage from Setup → System Overview.
Records in a certified managed package you install don’t count against your org’s allocation. But if the package developer later removes records, the deprecated rows stay in your org and start counting against your allotment until you delete them.
Real-World Patterns Worth Knowing
Custom metadata types shine wherever behavior should be configurable and portable:
- Mappings. Associate values across objects — cities to regions, product codes to GL accounts, country to ISO code — in a single readable table.
- Business rules. Pair records with a bit of Apex or flow logic to route payments to the right endpoint, apply tier-based discounts, or select an integration credential by environment.
- Feature toggles. A Checkbox per feature lets you switch behavior on or off per environment and promote the toggle with your deployment — no code change to flip it.
- Trigger and automation frameworks. Many modern frameworks store their trigger configuration — which handler runs, in what order, and when to bypass it — in custom metadata, so the automation is administered from Setup rather than edited in code.
The common thread: configuration lives as metadata, so it version-controls, packages, and promotes cleanly from sandbox to production alongside the functionality that reads it.
Custom Metadata Types: Exam Tips
High-yield facts that show up across App Builder and Developer exams.
- Records are metadata. They deploy with change sets and packages. Custom object and custom setting records do not — only the definition ships.
- Suffix is
__mdt. Fields use__c; records take no suffix. - SOQL is unlimited against custom metadata and doesn’t count against the query limit — except queries selecting long text area fields, which do count.
- getInstance / getAll skip SOQL. They read from cache (Spring ’21+);
getAll()returns a Map keyed by DeveloperName. - No standard DML. Apex creates and updates records only via
Metadata.Operations.enqueueDeployment— asynchronously, with aMetadata.DeployCallback. - Apex can’t delete custom metadata records — create and update only.
- Visible in tests without
SeeAllData, unlike custom settings and data. - Metadata Relationship is the relationship field type; master-detail is not available.
- Protected vs public. Subscribers can’t add records to a protected installed type; it must be public.
- Limits: 200 types/org, 100 fields/type, 10 million characters/org.
Put custom metadata types to the test
Scenario questions on custom metadata, custom settings, and declarative app config — the way the App Builder exam frames them.
Frequently Asked Questions
What is a custom metadata type in Salesforce?▾
How are custom metadata types different from custom settings?▾
Do custom metadata type records deploy with change sets and packages?▾
Do SOQL queries on custom metadata types count against governor limits?▾
Can you create or update custom metadata records with Apex?▾
Are custom metadata records visible in Apex test classes?▾
How many custom metadata types can you have in an org?▾
Official References
Verify the details against Salesforce’s own documentation: the Custom Metadata Type Fields and Custom Metadata Allocations pages in Salesforce Help, the Retrieving and Deploying Metadata reference for the Apex Metadata API, and the Custom Metadata Types module on Trailhead.
All information verified against the official Salesforce Summer ’26 (API v67.0) Help & Developer documentation. Study smarter at CertifySF.com.
