Custom Metadata is very good addition to Salesforce. They allow us to use records to configure our app without worrying about migrating those records to other orgs. We can deploy the records of custom metadata types from a sandbox with change sets or packaged in managed packages instead of transferring them manually.
Those records which needs to be consistent in sandbox/production can be keep in Custom Metadata. They also support relationship fields and long text area and custom field, Which is a great addition in comparison of custom setting.

But there is Major drawback, which is we cannot perform DML operations on them. So I have created a utility which we can use to create update custom Metadata using Apex.
public class MetaDataUtility {
public static String upsertMetadata(List<sObject> customMetadataList ) {
//Create Deployment container for custom Metadata
Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
for(sobject sObMD : customMetadataList) {
//Get metadata object name and details
String sObjectname = sObMD.getSObjectType().getDescribe().getName();
//Create custom Metadata instance
Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata();
String recordName = String.valueOf(sObMD.get('DeveloperName')).replaceAll(' ','_');
customMetadata.fullName = sObjectname +'.'+recordName;
customMetadata.label = (String)sObMD.get('MasterLabel');
//Get all fields
schema.SObjectType sobjType = Schema.getGlobalDescribe().get(sObjectname );
Map<String, Schema.sObjectField> sObjectFields = sobjType.getDescribe().fields.getMap();
Set<String> skipFieldSet = new Set<String>{'developername','masterlabel','language','namespaceprefix', 'label','qualifiedapiname', 'id'};
// Use getPopulatedFieldsAsMap to get the populate field and iterate over them
for(String fieldName : sObMD.getPopulatedFieldsAsMap().keySet()) {
if(skipFieldSet.contains(fieldName.toLowerCase())|| sObMD.get(fieldName) == null)
continue;
Object value = sObMD.get(fieldName);
//create field instance and populate it with field API name and value
Metadata.CustomMetadataValue customField = new Metadata.CustomMetadataValue();
customField.field = fieldName;
Schema.DisplayType valueType = sObjectFields.get(fieldName).getDescribe().getType();
if (value instanceof String && valueType != Schema.DisplayType.String)
{
String svalue = (String)value;
if (valueType == Schema.DisplayType.Date)
customField.value = Date.valueOf(svalue);
else if(valueType == Schema.DisplayType.DateTime) {
//DateTime is a special case which we need to handle carefully. It is working in my case you need to handle it.
try{
String d1 = svalue;
list<String> d2 = d1.split('-');
list<integer> timeComponent = new list<integer>();
timeComponent.add(Integer.valueOf(d2[0]));
timeComponent.add(Integer.valueOf(d2[1]));
timeComponent.add(Integer.valueOf(d2[2].left(2)));
String t = d2[2].substringBetween('T','.');
list<String> time1 = t.split(':');
timeComponent.add(Integer.valueOf(time1[0]));
timeComponent.add(Integer.valueOf(time1[1]));
timeComponent.add(Integer.valueOf(time1[2]));
Datetime dt = Datetime.newInstance(timeComponent[0],timeComponent[1],timeComponent[2],timeComponent[3],timeComponent[4],timeComponent[5]);
customField.value = dt;
}
catch(exception ex){}
}
else if (valueType == Schema.DisplayType.Percent || valueType == Schema.DisplayType.Currency)
customField.value = Decimal.valueOf(svalue);
else if (valueType == Schema.DisplayType.Double)
customField.value = Double.valueOf(svalue);
else if (valueType == Schema.DisplayType.Integer)
customField.value = Integer.valueOf(svalue);
else if (valueType == Schema.DisplayType.Base64)
customField.value = Blob.valueOf(svalue);
else
customField.value = svalue;
}
else
customField.value = value;
//Add fields in the object, similar to creating sObject instance
customMetadata.values.add(customField);
}
//Add metadata in container
mdContainer.addMetadata(customMetadata);
}
// Callback class instance
CustomMetadataCallback callback = new CustomMetadataCallback();
// Enqueue custom metadata deployment
// jobId is the deployment ID
Id jobId = Metadata.Operations.enqueueDeployment(mdContainer, callback);
return jobId;
}
//use it to pass single metadata instance
public static String upsertMetadata(sObject customMetadata ) {
return upsertMetadata(new List<sObject>{customMetadata} );
}
}
We also need a callback class as this is asynchronous process
public class CustomMetadataCallback implements Metadata.DeployCallback {
public void handleResult(Metadata.DeployResult result,
Metadata.DeployCallbackContext context) {
if (result.status == Metadata.DeployStatus.Succeeded) {
System.debug('success: '+ result);
} else {
// Deployment was not successful
System.debug('fail: '+ result);
}
}
}
We can use callback class to check status or can use deployment page.
(From Setup, enter Deployment Status in the Quick Find box, then select Deployment Status.)
OR
/changemgmt/monitorDeploymentsDetails.apexp?retURL=/changemgmt/monitorDeployment.apexp&asyncId=JOBID
Now using this utility is very simple. Just create custom Metadata List and pass it in Method.
//Create Records
List<Test_metadata__mdt> MetadataList = new List<Test_metadata__mdt>();
MetadataList.add(new Test_metadata__mdt(MasterLabel='Demo 1', DeveloperName = 'Demo 1', Name__c= 'Hello World', Account_Number__c = 12345678, Date__c = Date.today().addDays(-1), Phone__c = '1234567980'));
MetadataList.add(new Test_metadata__mdt(MasterLabel='Demo 2', DeveloperName = 'Demo 2', Name__c= 'newstechnologystuff.com', Account_Number__c = 12345678, Date__c = Date.today().addDays(-1), Phone__c = '1234567980'));
String jobId = MetaDataUtility.upsertMetadata(MetadataList);
//Update records
List<Test_metadata__mdt> MetadataList1 = [SELECT MasterLabel, DeveloperName,Name__c, Account_Number__c FROM Test_metadata__mdt]; //MasterLabel, DeveloperName is required when query as we need them in update
MetadataList1[0].Account_Number__c = 986532;
MetadataList1[0].Account_Number__c = 659823;
String jobId2 = MetaDataUtility.upsertMetadata(MetadataList1);
So we can use this Utility with any Custom Metadata type to create update Custom Metadata using Apex, it is similar to normal DML method except the asynchronous part. You can find code in github repo as well.
Check my post on Lightning Web Components here.
Did you like this post or want to add anythings. Let me know in comments section. Happy programming 🙂
Did you ever have problems with custome metadata deploys being stuck in “Pending Deployment” status like forever ?
Nope I didn’t face this issue. Is it happening always?
Can you Explain , how can i create Approval Process through Apex Class Using MetaData Api.
I don’t have sample, but you can use Metadata Wrapper class to create.
I am trying to build something similar to clone an existing MDT record but having issues with MDT records which Have Entity and Field relationships. The getDescribe does not really have any indication that this is what they are so when I use dynamic apex to generate a SOQL of the existing record, these fields come as . but look like Strings for all intents and purposes. Any ideas?