How do I insert records in Custom Metadata Type through Apex?

Custom Metadata Type records are considered Metadata, and as such can't be inserted or updated directly via standard DML operations. Trying to do so will result in a TypeException with a message like:

System.TypeException: DML operation INSERT not allowed on ISVNamespace__MetadataTypeName__mdt

The Metadata namespace was added to Apex in Summer'17/v40.0 and provides the ability to deploy certain types of metadata asynchronously without having to resort the to Metadata API.

There is a good example of how this works in the Metadata Operations - Deploy Metadata documentation.

public class CreateMetadata{
  public void updateAndDeployMetadata() {
    // Setup custom metadata to be created in the subscriber org.
    Metadata.CustomMetadata customMetadata =  new Metadata.CustomMetadata();
    customMetadata.fullName = 'ISVNamespace__MetadataTypeName.MetadataRecordName';

    Metadata.CustomMetadataValue customField = new Metadata.CustomMetadataValue();
    customField.field = 'customField__c';
    customField.value = 'New value';

    customMetadata.values.add(customField);

    Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
    mdContainer.addMetadata(customMetadata);

    // Setup deploy callback, MyDeployCallback implements
    // the Metadata.DeployCallback interface (code for
    // this class not shown in this example)
    MyDeployCallback callback = new MyDeployCallback();

    // Enqueue custom metadata deployment
    Id deployRequestId = Metadata.Operations.enqueueDeployment(mdContainer, callback);
  }
}

Note that this is an asynchronous operation and you need to provide a callback Apex class that implements the Metadata.DeployCallback interface.

This would be something like:

public class MyDeployCallback implements Metadata.DeployCallback {
    public void handleResult(Metadata.DeployResult result,
                             Metadata.DeployCallbackContext context) {
        Id jobId = context.getCallbackJobId();

        
        switch on (result.status) {
            when Succeeded {
                // Deployment was successful
            }
            when SucceededPartial {
                // The deployment succeeded, but some components might not have been successfully deployed. Check Metadata.DeployResult for more details.
            }
            when Failed {
                // Deployment was not successful
            }
            when Canceled {

            }
            when Pending, InProgress, Canceling {
                // Queued or state changing
            }
        }
    }
}

The DeployResult that comes back via the callback has a number of useful properties that correspond to monitoring an API based Metadata deployment result.


Generally you should rely on the callback to monitor the deployment and then take further actions. However, if you want to be less asynchronous you can use the DeployResultId and the Toolking API to speed things along.

This can't be in the same transaction as the enqueueDeployment as it will block subsequent callouts.

This example pulls the resulting MetadataComponentId out of the successfully deployed components.

public void checkDeployStatus(Id deployRequestId) {
    HttpRequest req = new HttpRequest();
    req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());
    req.setHeader('Content-Type', 'application/json');

    req.setMethod('GET');     
    String instanceName = System.Url.getSalesforceBaseUrl().toExternalForm();
    // You might need to adjust instanceName if you are working from Visualforce
    req.setEndpoint(instanceName + '/services/data/v49.0/metadata/deployRequest/' + deployRequestId + '?includeDetails=true');
    
    Http http = new Http();
    try {
        HTTPResponse res = http.send(req);                 

        if(res.getStatusCode() == 200) {
            String responseBody = res.getBody(); 

            DeployResultJson jsonResult = (DeployResultJson)JSON.deserialize(responseBody, DeployResultJson.class);

            if(jsonResult.deployResult.details != null) {
                for(Metadata.DeployMessage dm : jsonResult.deployResult.details.componentSuccesses) {
                    if(dm.fileName == 'package.xml') {
                        continue;
                    }

                    Id newMetadataComponentId = dm.Id;

                }
            }
        }
    } catch(System.CalloutException e) {
        // Exception Handling
    }
}

// Support unpacking via JSON.deserialize from deployRequest metadata api call
public class DeployResultJson {       
    public String id;
    public Object validatedDeployRequestId;
    public Object deployOptions;
    public Metadata.DeployResult deployResult;
}

Since DML Operations cannot be performed on Custom Metadata Type. Each change triggering a record creation is treated as a deployment.

More details to implement this can be found Here!