When you have more complex custom metadata needs or multiple custom metadata types, the UI requirements for users to manage records may also need to be more sophisticated. For example if you wish to build a setup wizard or type of visual editor. Much like you see with the Flow or Process Builder tools. So if you find you want to build your own UI for custom metadata records what is involved?
The native Apex Metadata API allows for Layout metadata as well as Custom Metadata records to be manipulated directly in Apex, without the traditional need for callouts or elevated permissions. Due to its generic API design, the data types for custom metadata records are represented as generic name/value pairs. Because of this it does require a bit more coding than you might expect to update records. Certainly compared to the SObject and DML oriented experience you get when working with Custom Object records in Apex. There is also the fact that it is async only, requiring careful transfer of results received via callbacks back to the user.
Custom Metadata Services (CMS) is a small library created to wrap the native Apex Metadata API to leverage the native SObject types for custom metadata in a more DML orientated way. It also aims to simplify the handling of the async results when developing clients in Lightning, inspired in this case by Lightning Data Services.
Updating records in Apex
When you create a Custom Metadata Type under the Setup menu, the platform automatically generates a corresponding SObject type you can use within Apex. This so that you can query those records using regular SOQL.
List records = [select DeveloperName, Label, DefaultNotification__c, Alias__c from WidgetPreset__mdt];
Using the native SObjectType and the related field references (SObjectField tokens) is preferable to using String literals from a referential integrity perspective and also namespace management for packaged code. So CMS uses these whenever possible. The following code will upsert a Custom Metadata record.
String deployId = CustomMetadata.Operations .callback( // Simple callback that outputs to debug new DebugCallback()) .enqueueUpsertRecords( // Custom Metadata object type WidgetPreset__mdt.getSObjectType(), new List<Map> { // Custom Metadata record new Map { WidgetPreset__mdt.DeveloperName => 'BluetoohToothbrush', WidgetPreset__mdt.Label => 'Bluetooh Toothbrush', WidgetPreset__mdt.DefaultNotification__c => 'Good day!', WidgetPreset__mdt.Alias__c => 'wdbtt' } } ) .deployId;
- The only operation currently supported is an upsert. The use of this terminology reflects the fact that the native Metadata API deploys a list of records that will either insert or update records.
- Because its not possible to assign values directly to __mdt SObject fields, the above code uses an Apex map that references the fields via their SObjectField tokens.
The native Apex Metadata API uses a background job to update the records so the results are not know immediately to the calling code. CMS tailors the DeployCallback interface with its own base class which is more orientated around custom metadata record results. CMS also uses its own callback ID rather than the native one, allowing the client to more easily identify the corresponding callback.
public class DebugCallback extends CustomMetadata.SaveResultCallback { public override void handleResult(String deployId, List results) { System.debug('Deploy Id is ' + deployId); for(SaveRecordResult result : results) { System.debug('Fullname of custom metadata reocord is ' + result.fullName); System.debug('Success is ' + result.success); if(!result.success) { System.debug('Message is' + result.message); } } } }
The callback method also supports the ability to send a Platform Event instead. This is preferable in cases where you are building UI’s, since the UI can listen for the callback directly. The Metadata Deployment platform event object is included in the library.
String deployId = CustomMetadata.Operations .callback( // Platform event for deploy status MetadataDeployment__e.getSObjectType(), MetadataDeployment__e.DeploymentId__c, MetadataDeployment__e.Result__c) .enqueueUpsertRecords( // Custom Metadata object type WidgetPreset__mdt.getSObjectType(), new List<Map> { // Custom Metadata record new Map { WidgetPreset__mdt.DeveloperName => 'BluetoohToothbrush', WidgetPreset__mdt.Label => 'Bluetooh Toothbrush', WidgetPreset__mdt.DefaultNotification__c => 'Good day!', WidgetPreset__mdt.Alias__c => 'wdbtt' } } ) .deployId;
Building a Lightning UI
Here is a simple UI built with CMS, to demonstrate loading, saving and error handling.
When building UIs you can utilise the Apex code above from your server side controller logic. Though as an alternative, CMS includes a Lightning component for use within client side controllers. The design of this component follows that of the force:recordData component used for managing Custom Object records. It is currently aimed at single record retrieval and updates. Here is the markup for the above component.
The metadataRecordData component has its own Apex controller leveraging the CMS Apex API above and then listens for the MetadataDeployment__e event (included). This is transparent to the consumers of the component. The controller code interacts with the saveRecord method and metadataRecordDataResult event.
Building a Visualforce UI
It is possible to include Lightning Components in Visualforce pages using Lightning Out and thus reuse the above code in both Classic and Lightning Experience.
However if you still prefer to develop using Visualforce or simply have a number of pages you’d rather continue to develop for a while. You can still utilise the Platform Event mechanism using the client side library called cometd. The sample code for the page below can be found in the GitHub repo.
Summary
I plan on utilizing CMS to update the rollup tools UI in the future and avoid the need for callouts (and related permissions) when using its UI. In full disclosure I would also classify the library as alpha at this stage, its still needs a little more refinement and robustness adding. It is open source, so free to join me in fleshing it out further!
August 29, 2017 at 9:40 am
Nice Article. waiting for you to update the sample code with Visualforce example, I have issues there with displaying errors/Warning message on click of commandLink.
September 2, 2017 at 8:45 pm
I have updated the repo with a Visualforce example.
August 29, 2017 at 11:14 am
I’ve wondered about push topics rather than platform events for some UI like notification. It depends on what you’re after. A push topic can be scoped by user, so you can notify only the person at the keyboard who’s interested. That said, a general purpose “Some metadata has been deployed” message seems generally useful in the way that any “Something changed” message is useful.
August 29, 2017 at 11:14 am
I’ve wondered about push topics rather than platform events for some UI like notification. It depends on what you’re after. A push topic can be scoped by user, so you can notify only the person at the keyboard who’s interested. That said, a general purpose “Some metadata has been deployed” message seems generally useful in the way that any “Something changed” message is useful.
August 29, 2017 at 11:23 am
Good idea but I dont think Push topics are supported for CMD
August 29, 2017 at 11:25 am
Not as yet, but it’s a great idea, thanks Richard.
August 30, 2017 at 3:24 pm
I was thinking of something I’d read while reading about streaming, and it could have been the Generic Streaming support (https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/generic_streaming_intro.htm). Unfortunately I can’t see how to create events from Apex here 😦
Pingback: Platform Events and Lightning Components | Andy in the Cloud
December 28, 2017 at 4:19 pm
This is great and we just added to our org. I’m now using it (in a batchable finish() ) to update in custom metadata a datetime filter to use in the nth+1 execution of the batchable class.
One suggestion for the Platform Event MetadataDeployment__e object: Add a field called Context__c , provided in the callback() method so the caller can communicate with the PE subscriber something about the context of why the custom metadata deployment service was invoked. Right now you just have DeploymentId__c, Result__c and implicit owner ‘Automated Process’.
December 28, 2017 at 10:52 pm
Glad your finding it useful! This is a great idea as well. Would you please do me a favor and add it to the GitHub repository as an Issue, I will flag it as an enhancement request. 👍🏻
February 9, 2018 at 4:04 am
Great solution as always. I was unable to Deploy to my org using the button. I created an issue for that: https://github.com/afawcett/custommetadataapi/issues/5
Pingback: Salesforce DX, Packages and Open Source | Andy in the Cloud
Pingback: Tools | Andy in the Cloud – Kay Scott's Blog
May 16, 2019 at 2:01 pm
somehow, your stylesheet for the apex markup has gone bad as the code samples are not valid APEX
May 16, 2019 at 6:20 pm
Yeah it’s a wordpress bug sadly, drives me nuts.
Pingback: Around the Web – 20170901 – WIPDeveloper.com
October 8, 2021 at 11:59 am
Nice, can any user run this without errors? Or the user needs to have a permissions like Modify Metadata to be able to deploy?
October 9, 2021 at 9:20 am
Yes they will need Modify Metadada – no way to bypass that on the platform thankfully. 👍🏻
Pingback: How to query records processed between last batch job if I’m running batch class five times a day – GrindSkills