10

I want to write a trigger on a custom object (Template__c) such that it invokes the creation or update of Custom metadata type records. Since the fields are not writeable, how do I achieve this?

I tried using the "CustomMetadataValue". I can see the values assigned in Debug when I run in Anonymous window but the values do not get created in actual.

3 Answers 3

18

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;
}
3
  • can I create Metadata inside Apex test class? I do get an error when I follow above approach in test class- System.AsyncException: Metadata cannot be deployed from within a test. Is there any other way to create test metadata?
    – SfdcBat
    Commented Aug 12, 2020 at 14:59
  • 1
    @SfdcBat No. A Test class can't do actual metadata deployments. I've had to wrap Metadata.Operations.enqueueDeployment with a Test.isRunningTest() and return a mock DeployRequest Id in a test context. And the remainder of the tests rely on mock custom metadata type records. It's a bit hacky, but it does work. Commented Aug 12, 2020 at 23:23
  • 1
    I know this is just an example, but I wanted to clairify the label is required. I was getting the error: "Required fields are missing: [MasterLabel]" Here is the update: ` ... // Setup custom metadata to be created in the subscriber org. Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata(); customMetadata.fullName = 'ISVNamespace__MetadataTypeName.MetadataRecordName'; customMetadata.label = 'Metadata Record Name';` Commented Dec 6, 2023 at 18:12
5

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!

1
  • 2
    Code sample would be nice, but the link provides code sample (though most of the content is copied from Apex documentation: Example: Deploy Metadata) Commented Jan 29, 2019 at 20:17
1

Apex code can create, read, and update (but not delete) custom metadata records, as long as the metadata is subscriber-controlled and visible from within the code's namespace.

DML operations aren’t allowed on custom metadata in the Partner or Enterprise APIs. With unpackaged metadata, both developer-controlled and subscriber-controlled access behave the same: like subscriber-controlled access.

Professional and Group Edition orgs can create, edit, and delete custom metadata records only from types in installed packages.

Refer Access Custom Metadata Records Programmatically for more insights.

2
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Adrian Larson
    Commented Feb 1, 2018 at 20:50
  • 10
    ^ Chat is not found...comments could have stayed for late-comers Commented Jan 29, 2019 at 19:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .