Updating Subscriber-Editable Custom Metadata Fields via Apex

Question:
I have created a managed package in a Developer org containing custom objects, Apex classes, and custom metadata types (CMT). The custom metadata type records are set as public, and the fields I need to modify are marked as “Subscriber Editable.” After installing the package in a subscriber sandbox, I tried updating these fields using the Metadata API from Apex code within the same managed package. However, the updates failed, and I can only modify these fields through the setup page in the Salesforce UI.
Is there a way to modify subscriber-editable custom metadata type record fields programmatically in the subscriber org using Apex code?
CRS Info Solutions offers industry-leading Salesforce online training with real-time projects, certification guidance, interview coaching, and a practical approach to make you job-ready.
Answer:
Yes, it is possible to modify subscriber-editable fields of custom metadata type (CMT) records in a subscriber org using Apex code, but this involves specific considerations.
Here’s how you can do it effectively:
1.Using the Metadata.Operations.enqueueDeployment
Method:
The Metadata.Operations.enqueueDeployment
method enables you to update CMT records programmatically. This approach is asynchronous, meaning the changes are not applied immediately. You need to build a Metadata.DeployContainer
and populate it with the desired updates.
Here is a detailed implementation:
public class SalesforceOrgSettingsHandler implements Metadata.DeployCallback {
public static Id updateRecord(Salesforce_Org_Setting__mdt cmtRecord) {
Metadata.CustomMetadata customMetadataRecord = new Metadata.CustomMetadata();
customMetadataRecord.fullName = cmtRecord.NamespacePrefix + '__Salesforce_Org_Setting__mdt.' + cmtRecord.QualifiedApiName;
customMetadataRecord.label = cmtRecord.Label;
customMetadataRecord.values = new List<Metadata.CustomMetadataValue>();
Map<String, Object> fields = cmtRecord.getPopulatedFieldsAsMap();
for (String fieldName : fields.keySet()) {
if (fieldName.startsWith(cmtRecord.NamespacePrefix)) {
Metadata.CustomMetadataValue cmtProperty = new Metadata.CustomMetadataValue();
cmtProperty.field = fieldName;
cmtProperty.value = cmtRecord.get(fieldName);
customMetadataRecord.values.add(cmtProperty);
}
}
Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
mdContainer.addMetadata(customMetadataRecord);
Id jobId;
if (!Test.isRunningTest()) { // Deployment cannot be enqueued during tests
jobId = Metadata.Operations.enqueueDeployment(mdContainer, new SalesforceOrgSettingsHandler());
}
return jobId;
}
public void handleResult(Metadata.DeployResult result, Metadata.DeployCallbackContext context) {
System.debug('Metadata Deployment Status: ' + (result.status == Metadata.DeployStatus.Succeeded));
}
}
Explanation:
- Purpose: This class handles updating a custom metadata type (CMT) record via the
Metadata.Operations.enqueueDeployment
method. The deployment operation is asynchronous and needs to be enqueued. - Key Steps:
- A
Metadata.CustomMetadata
object is created and populated with the full name, label, and values of the fields to be updated. - The
getPopulatedFieldsAsMap()
method fetches all populated fields from the providedSalesforce_Org_Setting__mdt
record. This ensures you only update fields with actual values. - Each field is mapped to a
Metadata.CustomMetadataValue
object, which holds the field name and value for the update. - The
Metadata.DeployContainer
object acts as a container for the custom metadata changes and is passed to theenqueueDeployment
method, initiating the deployment. - The
handleResult
method processes the deployment result, logging whether it succeeded or failed.
- A
To update your preferences:
public void savePreferences() {
Salesforce_Org_Setting__mdt cmtRecord = Salesforce_Org_Setting__mdt.getInstance('Defaults');
cmtRecord = cmtRecord.clone(false, false, false, false);
cmtRecord.Enable_Debug_Logs__c = String.valueOf(EnableDebugLogs);
cmtRecord.Synchronous_Threshold1__c = String.valueOf(SyncThreshold1);
cmtRecord.Synchronous_Threshold2__c = String.valueOf(SyncThreshold2);
cmtRecord.Batch_Size__c = String.valueOf(BatchSize);
Id jobId = SalesforceOrgSettingsHandler.updateRecord(cmtRecord);
}
Explanation:
- Purpose: This method prepares a CMT record with updated values and triggers the deployment to save the changes.
- Key Steps:
- The
Salesforce_Org_Setting__mdt.getInstance('Defaults'
)
retrieves the default CMT record. - The
clone
method creates a copy of the record, ensuring no reference conflicts with the original record. - Subscriber-editable fields (
Enable_Debug_Logs
__c
,Synchronous_Threshold1__c
, etc.) are updated with new values. - The updated record is passed to the
updateRecord
method of theSalesforceOrgSettingsHandler
class, which handles the deployment.
This approach requires the user in the subscriber org to have the following system permissions:
- The
- Modify Metadata Through Metadata API Functions
- Customize Application
- If your managed package is not Salesforce-certified, the subscriber org must enable Deploy Metadata from Non-Certified Package Version via Apex from Setup > Apex Settings.
2.Alternative Using the updateMetadata
Method:
You can also use the updateMetadata
method from the MetadataService
class. This method is synchronous but has limitations in terms of flexibility.
Here’s an example:
public void savePreferences() {
try {
MetadataService.MetadataPort oMetadataPort = MetadataServiceExamples.createService();
MetadataService.CustomMetadata oCustomMetadata = new MetadataService.CustomMetadata();
oCustomMetadata.fullName = 'MyNamespace__Salesforce_Org_Setting__mdt.Defaults';
oCustomMetadata.label = 'Defaults';
oCustomMetadata.values = new List<MetadataService.CustomMetadataValue>{
createCustomMetadataValue('MyNamespace__Enable_Debug_Logs__c', String.valueOf(EnableDebugLogs)),
createCustomMetadataValue('MyNamespace__Synchronous_Threshold1__c', String.valueOf(SyncThreshold1)),
createCustomMetadataValue('MyNamespace__Synchronous_Threshold2__c', String.valueOf(SyncThreshold2)),
createCustomMetadataValue('MyNamespace__Batch_Size__c', String.valueOf(BatchSize))
};
List<MetadataService.SaveResult> results = oMetadataPort.updateMetadata(new MetadataService.Metadata[] { oCustomMetadata });
if (results[0].success) {
System.debug('Metadata Updated Successfully');
} else {
System.debug('Error: ' + results[0].errors[0].message);
}
} catch (Exception e) {
System.debug('Error saving preferences: ' + e.getMessage());
}
}
Explanation:
- Purpose: This method uses the synchronous
MetadataService
API to update a CMT record directly. - Key Steps:
- A
MetadataService.MetadataPort
object is created to interface with the Metadata API. - A
MetadataService.CustomMetadata
object is populated with the record’s full name, label, and updated values. - The
updateMetadata
method sends the updated metadata to Salesforce for processing. The result is checked for success or errors.
- A
- Limitations:
Unlike theenqueueDeployment
method, this approach is synchronous and suitable for simpler updates but less efficient for handling multiple records. - Important Notes:
- These methods depend on the subscriber org having the required permissions.
- The Metadata API actions will appear under Setup > Deployment Status in the subscriber org.
- Use the asynchronous approach for complex updates or when handling multiple records.
Summing up:
Updating subscriber-editable custom metadata fields via Apex is a nuanced process that involves leveraging the Metadata API to modify records programmatically. While fields marked as subscriber-editable allow updates, direct changes through Apex require permissions like “Modify Metadata Through Metadata API Functions” and the proper use of tools such as Metadata.Operations.enqueueDeployment
for asynchronous updates or MetadataService.updateMetadata
for synchronous ones.
These methods enable dynamic updates to metadata records, ensuring flexibility and customization in managed packages, but they come with requirements like deployment permissions in the subscriber org. By understanding the appropriate approach and limitations, developers can efficiently update metadata while maintaining compliance with Salesforce’s managed package restrictions.
Jumpstart Your Salesforce Career with Training in Pune
Ready to unlock new career opportunities with Salesforce? CRS Info Solutions offers Salesforce training in Pune, designed to provide you with the skills and knowledge needed to excel in the Salesforce ecosystem. Our courses cover all critical areas, including Salesforce Admin, Developer, and AI modules, with a focus on real-time project-based learning. Led by industry professionals, our training ensures you gain practical experience that prepares you for the challenges of the real world. Whether you’re new to Salesforce or looking to advance your skills, our structured approach helps you become job-ready.
Our Salesforce training in Pune emphasizes hands-on learning, personalized mentorship, and comprehensive resources. You’ll receive detailed class notes, expert guidance for certification, and interview preparation to ensure you’re ready to step into your next Salesforce role.
Don’t miss the chance to accelerate your career—enroll today and join our free demo session to kickstart your Salesforce journey!