Custom Metadata records can be created, updated and deleted synchronously using Apex web services. This is simpler and more responsive for interactive scenarios, instead of using the native utility which requires asynchronous callback handlers.

This custom class supports the creation of Custom Metadata records from Apex code. It calls the Metadata API synchronously with immediate results for each record.

Example usage:

First paste the CustomMetadataClient.cls into your org.

Then call the methods to manipulate MDT records.

Unit tests here: CustomMetadataClientTest.cls

//create a metadata record
Database.UpsertResult result = CustomMetadataClient.upsertMetadata(
    new Map<SObjectField,Object>{
        Custom__mdt.DeveloperName => 'Developer_Name',
        Custom__mdt.MasterLabel => 'Master Label',
        Custom__mdt.Position__c => 3
//delete a metadata record
Database.DeleteResult result = CustomMetadataClient.deleteMetadata(
    new List<String>{'Developer_Name'}

How to construct the records?

Use a Map<SObjectField,Object> to represent each record. This is more verbose than constructing a normal SObject, but necessary because Apex complains that Custom__mdt fields are not writeable.

What does each metadata operation return?

An instance of Database.UpsertResult is returned by upserts, and an instance of Database.DeleteResult is returned by deletes.

We actually coerce the responses back into these native types so they make MDT operations look and feel more like normal SObject operations.

  • getId() is the FullName (as opposed to the ID)
  • getErrors() lists details of any error messages
  • isSuccess() indicates if each record succeeded
  • isCreated() indicates new vs existing records

The intent is to provide the Metadata API response in familiar terms without introducing a new type.


Note with all API operations, a remote site setting is needed.

HTTP callouts are not transactions and cannot be rolled back!

Related Posts

Last modified: 10th September 2019


Yasar Shaikh 

Very nice post! I think, some changes needed with given code(in gist[error at getter of protocolAndHost] and in post at DeleteMetadata() code, actual code is expecting String; however here you have passed List of Strings).

Eric Kintzer 

Nice! I’ve used the AndyintheCloud Custom Metadata Services github package but that is asynchronous so this is a big improvement.
However, don’t the Mock inner classes need to be public so one’s testmethods can use them?