Apex Metadata API

Use cases for and notes on how to use the Apex Metadata API

Use Cases

Constraints

Usage Notes

Code Examples

Editing Page Layouts

Retrieve the Account Page Layout and add a new Field to it

public class UpdatePageLayout {
    public Metadata.Layout buildLayout() {
        List<Metadata.Metadata> layouts = Metadata.Operations.retrieve(
            Metadata.MetadataType.Layout,
            new List<String> {'Account-Account Layout'}
        );

        Metadata.Layout layoutMd = (Metadata.Layout) layouts.get(0);
        Metadata.LayoutSection layoutSectionToEdit = null;
        List<Metadata.LayoutSection> layoutSections = layoutMd.layoutSections;
        for (Metadata.LayoutSection section : layoutSections) {
            if (section.label == 'Account Information') {
                layoutSectionToEdit = section;
                break;
            }
        }

        // Add the field under Account info section in the left column
        List<Metadata.LayoutColumn> layoutColumns = layoutSectionToEdit.layoutColumns;
        List<Metadata.LayoutItem> layoutItems = layoutColumns.get(0).layoutItems;

        // Create a new layout item for the custom field
        Metadata.LayoutItem item = new Metadata.LayoutItem();
        item.behavior = Metadata.UiBehavior.Edit;
        // NOTE: Field API name goes here
        item.field = 'AMAPI__Apex_MD_API_sample_field__c';
        layoutItems.add(item);

        return layoutMd;
    }
}

Callback Handler for Deploying Metadata

public class PostInstallCallback implements Metadata.DeployCallback {
    public void handleResult(Metadata.DeployResult result,
        Metadata.DeployCallbackContext context) {

        if (result.status == Metadata.DeployStatus.Succeeded) {
            // Deployment was successful, take appropriate action.
            System.debug('Deployment Succeeded!');
        } else {
            // Deployment wasn’t successful, take appropriate action.
	    System.debug('Deployment Failed!');
        }
    }
}

Test Class for PostInstallCallback

@IsTest
public class MyDeploymentCallbackTest {
    @IsTest
    static void testMyCallback() {

        // Instantiate the callback.
        Metadata.DeployCallback callback = new PostInstallCallback();

        // Create test result and context objects.
        Metadata.DeployResult result = new Metadata.DeployResult();
        result.numberComponentErrors = 1;

        // NOTE: This is a null job ID. If you want to test a non-null Job ID, create a subclass
        // of DeployCallbackContext and provide own implementation (see below)
        Metadata.DeployCallbackContext context = new Metadata.DeployCallbackContext();

        // Invoke the callback's handleResult method.
        callback.handleResult(result, context);
    }
}

// DeployCallbackContext subclass for testing that returns myJobId.
public class TestingDeployCallbackContext extends Metadata.DeployCallbackContext {
    private Id myJobId = null; // Set to a fixed ID you can use for testing.
    public override Id getCallbackJobId() {
        return myJobId;
    }
}

Metadata Deployment Container

public class DeployMetadata {

    // Create metadata container
    public Metadata.DeployContainer constructDeploymentRequest() {
        Metadata.DeployContainer container = new Metadata.DeployContainer();

        // Add components to container
        Metadata.Layout layoutMetadata = new UpdatePageLayout().buildLayout();
        container.addMetadata(layoutMetadata);
        return container;
    }

    // Deploy metadata
    public void deploy(Metadata.DeployContainer container) {
        // Create callback.
        PostInstallCallback callback = new PostInstallCallback();

        // Deploy the container with the new components.
        Id asyncResultId = Metadata.Operations.enqueueDeployment(container, callback);
    }
}

Test Class for DeployMetadata

@IsTest
public class DeploymentTest {
    @IsTest
    static void testDeployment() {
        DeployMetadata deployMd = new DeployMetadata();

        Metadata.DeployContainer container = deployMd.constructDeploymentRequest();
        List<Metadata.Metadata> contents = container.getMetadata();
        System.assertEquals(1, contents.size());
        Metadata.Layout md = (Metadata.Layout) contents.get(0);

        // Perform various assertions the layout metadata.
        System.assertEquals('Account-Account Layout', md.fullName);
    }
}

Post-Install Script that Calls the Deployment

public class PostInstallScript implements InstallHandler {

    // Deploy post-install metadata
    public void onInstall(InstallContext context) {
        DeployMetadata deployUtil = new DeployMetadata();
        Metadata.DeployContainer container = deployUtil.constructDeploymentRequest();
        deployUtil.deploy(container);
    }
}

Creating New Custom Metadata Type Records

Metadata.CustomMetadata customMetadata =  new Metadata.CustomMetadata();
customMetadata.fullName = 'MyNamespace__MetadataTypeName.MetadataRecordName';

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

customMetadata.values.add(customField);

Metadata.DeployContainer container = new Metadata.DeployContainer();
container.addMetadata(customMetadata);
Id asyncResultId = Metadata.Operations.enqueueDeployment(container, null);

Updating Custom Metadata Type Records

public class VATController {
    public final List<VAT_Rate__mdt> VATs {get;set;}
    final Map<String, VAT_Rate__mdt> VATsByApiName {get; set;}

    public VATController() {
        VATs = new List<VAT_Rate__mdt>();
        VATsByApiName = new Map<String, Vat_Rate__mdt>();
        for (VAT_Rate__mdt v : [SELECT QualifiedApiName, MasterLabel, Default__c, Rate__c
                                FROM VAT_Rate__mdt]) {
                                    VATs.add(v);
                                    VATsByApiName.put(v.QualifiedApiName, v);
                                }
    }

    public PageReference save() {

        // Create a metadata container.
        Metadata.DeployContainer container = new Metadata.DeployContainer();
        List<String> vatFullNames = new List<String>();
        for (String recordName : VATsByApiName.keySet()) {
            vatFullNames.add('VAT_Rate.' + recordName);
        }

        List<Metadata.Metadata> records = Metadata.Operations.retrieve(
            Metadata.MetadataType.CustomMetadata,
            vatFullNames
        );

        for (Metadata.Metadata record : records) {
            Metadata.CustomMetadata vatRecord = (Metadata.CustomMetadata) record;
            String vatRecordName = vatRecord.fullName.substringAfter('.');
            VAT_Rate__mdt vatToCopy = VATsByApiName.get(vatRecordName);

            // Update the values on the newly-fetched VAT Record
            for (Metadata.CustomMetadataValue vatRecValue : vatRecord.values) {
                vatRecValue.value = vatToCopy.get(vatRecValue.field);
            }

            // Add record to the container.
            container.addMetadata(vatRecord);
        }

        // Deploy the container with the new components.
        Id asyncResultId = Metadata.Operations.enqueueDeployment(container, null);

        return null;
    }
}

Security Implications

Further Reading

Tags