
How to Import Salesforce Custom Metadata Records using CSV/JSON
Importing Salesforce Custom Metadata Records can be really tricky. Forget about using the import wizard or dataloader to do that. These tools are amazing but simply do not support operations on Custom Metadata Records.
If you perform a quick search on Google about this theme, you probably will, at a certain point of your search, get into a guide that will tell you to use the Custom Metadata Loader. This tool may be useful if you have just s few records to import, and none of your records has a special character on it, otherwise, you certainly will fail after a lot of bugs and errors.
I've developed a class using the official Salesforce Documentation as guidelines to achieve that requirement, and I'll help you to import your Salesforce Custom Metadata Records following steps below:
1. Prepare your Data
You can use any online service that converts CSV into JSON. I recommend the service below:
https://csvjson.com/csv2json
Simply upload your CSV file containing the records you want to upload, click on CONVERT and copy the generated JSON.
1.1. Make your JSON inline
What we have to do next, is to parse that JSON into a single line, you can easily do that by accessing the following link:
https://jsonformatter.curiousconcept.com/
Simply paste your JSON, select COMPACT on the JSON Template Option, and click PROCESS. Copy the generated compact JSON and keep that safe for later steps.
2. Create the mdtImport Apex Class using the code below
//author: Roger Rosset //description: Upload custom metadata records using CSV/JSON public class mdtImport { public static void insertMetadata(String metaDataTypeName, String jsonString){ try { Integer count = 1; Metadata.DeployContainer mdContainer = new Metadata.DeployContainer(); JSONCsvTemplate csv = (JSONCsvTemplate)JSON.deserialize(jsonStringMdt(jsonString), JSONCsvTemplate.class); for(JSONCsvTemplate.mdtRecords item : csv.data.mdtRecordsList){ //Sets the custom metadata type you'll insert your records on //If you're using namespaces on your org set it here String nameSpacePrefix =''; Metadata.CustomMetadata rec = new Metadata.CustomMetadata(); String label = 'Record '+count; rec.fullName = nameSpacePrefix+metaDataTypeName+'.'+label; rec.label = label; //Sets the custom metadata custom fields to be inserted /* * Use the template below to setup any fields you want to: * Metadata.CustomMetadataValue fieldX = new Metadata.CustomMetadataValue(); //New instance fieldX.field = 'Custom_Field_Name__c'; //Custom Metadata Field you want to fill field1.value = item.JSON_Matching_Key_Value; //The matching key value on the wrapper rec.values.add(fieldX); //adds the value and the matching field * */ Metadata.CustomMetadataValue field1 = new Metadata.CustomMetadataValue(); field1.field = 'SubGroup__c'; field1.value = item.SubGroup; rec.values.add(field1); Metadata.CustomMetadataValue field2 = new Metadata.CustomMetadataValue(); field2.field = 'Description__c'; field2.value = item.Description; rec.values.add(field2); Metadata.CustomMetadataValue field3 = new Metadata.CustomMetadataValue(); field3.field = 'keyId__c'; field3.value = item.keyId; rec.values.add(field3); mdContainer.addMetadata(rec); count++; } Id jobId = Metadata.Operations.enqueueDeployment(mdContainer, null); system.debug('Container>>'+mdContainer); system.debug('Id>>'+jobId); return; } catch(Exception ex){ system.debug('Error on insert'); system.debug('Error:'+ex.getMessage()); } } private static String jsonStringMdt(String jsonString){ String resultJson = '{"data":{"mdtRecordsList":'+jsonString+'}}'; return resultJson; } private class JSONCsvTemplate{ private class Data{ private List<mdtRecords> mdtRecordsList; } private class mdtRecords{ private String keyId; private String SubGroup; private String Description; } private Data data; } }
As you can see you can execute the methods metaDataTypeName passing the metaDataTypeName and jsonString as attributes, where metaDataTypeName is the API Name of your Custom Metadata Type (ending with __mdt) and jsonString is the inline JSON that you've copied on step 1.1, But first, you have to adjust the attributes on the JSONCsvTemplate>mdtRecords to fit like your CSV database.
Don't forget to check out: Schedule Apex in Salesforce | Apex Developer Guide
3. Adjust the Wrapper class to your requirements
private class JSONCsvTemplate{ private class Data{ private List<mdtRecords> mdtRecordsList; } private class mdtRecords{ private String keyId; private String SubGroup; private String Description; } private Data data; }
As you can see above, we are uploading records that have 3 columns:
- keyId
- SubGroup
- Description
These values match exactly as my CSV database headers, and you can add, delete, or modify the template to meet your criteria. Keep in mind that your CSV database must fit the wrapper class, not only about the field names but also the primitive data types (Integer, String, Etc.)
CSV Database used:
4. Execute the method
After following all the previous steps and making sure you've created and saved the class on your Salesforce Org, all you have to do next is to execute the insertMetadataRecords
Get your Custom Metadata Type API Name (ending with __mdt) and your JSON String generated in step 1.1, and execute the method on the Apex Anonymous Window inside the developer console (CTRL+E)
Keep in mind that your JSON String may be really big, and to avoid problems, it's highly recommended you to place the method template and quotes before pasting the "superstring" on the parameter inside the method execution, as below:
Check out an amazing Salesforce tutorial video here: Salesforce Trailhead 2020 - Queueable Apex - Challenge
After preparing the method, simply paste your big JSON String inside the quotes, and click execute.
If you have checked the Open Log option, you should be taken to the log of this execution, where you can find the ID of your enqueued deploy, and check it's status on the deployment status of Salesforce on:
SETUP > DEPLOY > DEPLOYMENT STATUS
Attention
I've already tested the deploy of 600 records per execution, try to respect that limit to avoid errors.
Feel free to modify the code as you need to, keeping the credits if possible.
Hope it is useful.
Regards,
Roger Rosset
Based documentation:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_Metadata_Operations.htm
Hi I tried using your code, but get the following error: Name can only contain underscores and alphanumeric characters. It must be unique, begin with a letter, not include spaces, not end with an underscore, and not contain two consecutive underscores.
This has to do with Developer Name.
This is the code I have in Execute Anonymous:
mdtImport.insertMetadata('Netsuite_Country_State__mdt', '[{"DeveloperName":"United_States_AA","MasterLabel":"United_States_AA","To_Country":"United States","From_State":"AA","To_State":"Armed Forces Americas","Code":"AA"}]');
Can you please help.
DeveloperName and MasterLabel are the standard fields of the Metadata Object
Line number 10 : for(JSONCsvTemplate.mdtRecords item : csv.data.mdtRecordsList)
should be changed to
for(mdtRecords item : csv.data.mdtRecordsList)
Thank you.
Try BOFC - to export (Single or multiple) Custom Metadata Types at the same time in few clicks.