Are you an LLM? You can read better optimized documentation at /api/custom-metadata.md for this page in Markdown format
Custom Metadata
Two custom metadata types switch handlers off for the whole org, without code changes. A TriggerObject__mdt record covers every handler on one object. A TriggerHandler__mdt record under it covers one handler class. Create the records in Setup under Custom Metadata Types → Manage Records, or deploy them.
TriggerObject__mdt
One record per object.
| Field | Holds |
|---|---|
ObjectAPIName__c | the object's API name, such as Account or Invoice__c; case does not matter |
Bypass__c | checked: every handler on the object is off, in every context |
- The whole run is skipped. No handler, parent query or provider runs, and no Logger method is called.
- Also the parent of handler records.
TriggerHandler__mdtrecords need it, even withBypass__cunchecked.
TriggerHandler__mdt
One record per handler class to switch off on one object.
| Field | Holds |
|---|---|
TriggerObject__c | the TriggerObject__mdt record of the object |
ApexClassName__c | the handler's class name |
Bypass__c | checked: this handler is off on that object |
- In every context. A class registered in before insert and before update is off in both. To switch off one context only, use that context's Bypassable add-on.
- Only on that object. A class used on Account and on Contact needs a record under each object.
ApexClassName__c Format
The value is matched against the class name without its outer class. Case does not matter.
| Handler class | Values that match |
|---|---|
top-level AccountRatingPopulator | AccountRatingPopulator |
inner AccountRules.RatingPopulator | RatingPopulator or AccountRules.RatingPopulator |
- Same inner name, same switch.
AccountRules.RatingPopulatorandContactRules.RatingPopulatorare both switched off by either value, on that object. - A misspelled name matches nothing, and nothing warns you.
When They Are Read
- Once per transaction. The first
runorbypass()call reads all records with one query. Changes apply from the next transaction. - No SOQL limit cost. Custom metadata queries do not count toward the limit.
- Tests see the org's records. Tests that run the orchestrator can mock the query. See Testing.
Deploy Records
The file name is <Type>.<DeveloperName>.md-meta.xml, for example in force-app/main/default/customMetadata/.
xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Account</label>
<protected>false</protected>
<values>
<field>ObjectAPIName__c</field>
<value xsi:type="xsd:string">Account</value>
</values>
<values>
<field>Bypass__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
</CustomMetadata>xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>AccountRatingPopulator</label>
<protected>false</protected>
<values>
<field>TriggerObject__c</field>
<value xsi:type="xsd:string">Account</value>
</values>
<values>
<field>ApexClassName__c</field>
<value xsi:type="xsd:string">AccountRatingPopulator</value>
</values>
<values>
<field>Bypass__c</field>
<value xsi:type="xsd:boolean">true</value>
</values>
</CustomMetadata>- Declare
xmlns:xsd. Without it, the deployment fails with anUNKNOWN_EXCEPTIONthat names no component. TriggerObject__cholds the DeveloperName alone, such asAccount, notTriggerObject.Account.Bypass__c = truetakes effect on deploy. Keep such records out of production packages unless you mean it.
