Creating a Custom Metadata Type is similar to creating a custom object. The difference shows up at deployment time, where records travel with metadata changes instead of requiring separate data loads. Plan the field types, decide between Public and Protected visibility, and design the records as a deployable artifact from the start.
- Confirm Custom Metadata is the right tool
Use Custom Metadata for static configuration that should deploy with code, support cross-object relationships, and stay out of the data tier. Use Hierarchy Custom Settings if User or Profile inheritance is required. Use List Custom Settings only when production admin editing without deploy is critical.
- Create the Custom Metadata Type
Setup > Custom Metadata Types > New Custom Metadata Type. Enter label, plural label, and API name. The API name automatically gets the __mdt suffix. Choose Public or Protected visibility based on whether external code should access the records.
- Add custom fields
From the type detail page > Custom Fields & Relationships > New. Pick the field type. Metadata Relationship fields let one record reference another Custom Metadata record or an EntityDefinition/FieldDefinition. Picklist fields are supported, unlike on Custom Settings.
- Build records via the Manage button
Setup > Custom Metadata Types > the type > Manage Records > New. For each record, enter the Label, Developer Name (the primary key for getInstance lookups), and the field values. Records appear in the manifest as separate metadata files.
- Reference from Apex with getInstance or SOQL
Tax_Rate__mdt rate = Tax_Rate__mdt.getInstance(''US''); for direct lookup. List<Tax_Rate__mdt> rates = [SELECT Country__c, Rate__c FROM Tax_Rate__mdt]; for bulk read. Both access patterns are free against SOQL governor limits.
- Reference from formulas via the CustomMetadata global
Formula syntax: CustomMetadata.Tax_Rate__mdt.Default.Rate__c (prefixed with the formula-engine dollar global) in formula fields. This is the formula-engine path to Custom Metadata, useful for business rules that depend on configurable thresholds without touching Apex.
- Deploy via change set or metadata API
Include the Custom Metadata Type definition AND the individual records in the change set or package manifest. Each record is its own metadata file (Tax_Rate.US.md-meta.xml). The deployment moves both type and records to the target org.
- Version the metadata in source control
Custom Metadata records belong in the version-controlled repo alongside Apex classes. Use SFDX project format. Pull request reviews catch configuration changes the same way they catch code changes, which is the major workflow win over Custom Settings.
Public for org-wide access. Protected for managed-package-only access. Drives whether subscriber-org code can read the records.
Text, Number, Date, Checkbox, Picklist, Long Text Area, Metadata Relationship. Broader than Custom Settings; picklists and relationships work natively.
The primary key used by getInstance and formula lookups. Cannot be changed after creation without breaking references.
- Custom Metadata records are metadata. Inserts and updates via API require Metadata API or the Metadata.Operations Apex class, not standard DML. Apex DML on __mdt objects throws an error.
- Custom Metadata does not support User or Profile hierarchy. Use Hierarchy Custom Settings if different users need different values for the same setting.
- Flow Get Records on Custom Metadata counts against SOQL governor limits because Flow does not use the Apex cache. Cache results in Flow variables when reading multiple times.
- Protected visibility is irrevocable once a managed package is uploaded. Downgrading from Protected to Public breaks the package security boundary, so design carefully up front.
- Deploying Custom Metadata records requires explicit inclusion in the change set or package manifest. Forgetting to include records during deployment is a common cause of post-deployment behavior changes.