The upcoming Spring’14 Metadata API brings with it some great new architectural features.
- Ability to create, update and delete immediately without having to callback for the result
- Ability to read Metadata without having to use retrieve and unzip the response!
- Ability to rename Metadata components
- Plus a host of new Metadata types and extensions to existing
The most exciting for me is the first two, first up the ability to greatly simplify the use of the API, particularly in Apex. As per a previous blog post I covered the requirement to poll for the results of certain API operations via the checkSync and checkDeploy methods. Which involved the use of either Batch Apex or apex:actionPoller. Many posters on this blog and the GitHub repo found this awkward to code.
These original operations still exist and I’ll continue to support them, but we now have some much much easier variants that simply return immediately the result. Here is an example showing the new readMetadata operation in action!
// Read the layout MetadataService.Layout layout = (MetadataService.Layout) service.readMetadata('Layout', new String[] { 'Test__c-Test Layout' }).getRecords()[0]; // Add a button to the layout layout.customButtons = new List<String>{ 'googleButton' }; // Update the layout List<MetadataService.SaveResult> result = service.updateMetadata(new MetadataService.Metadata[] { layout });
With a true read facility in the API, imagine the possibilities with the Layouts alone, such as merging layouts, dynamically adding or removing buttons, fields, sections from a selection of layouts the user selections. If your developing a packaged application, providing a means to upgrade your users layouts and picklists automatically!
No Batch Apex, no apex:actionPoller to be seen! Its still early days and the official docs have not been announced, but from what I’d expect from how the readMetadata operation is designed, you’re going to be able to retrieve other types as well, such as ListView, Reports etc. Suddenly creating Setup wizards has become much easier, as well as custom Setup reporting tools, for example retrieving all the layouts and dumping the fields used in a Custom Object for reporting!
No More Zip File Handling!
The other really exciting thing here is we finally don’t need the help of JSZip JavaScript library to extract Metadata information from a retrieve operation call. While I’m still quite pleased with how i managed to abstract this. i’m very pleased that we can now read Metadata in none Visualforce contexts, such as Batch Apex or Apex Triggers.
Automating the Changes to the Generated Code
As always the code generated by the platform via the WSDL2Apex ‘Generate Apex from WSDL‘ button didn’t quite generate the Apex needed. Each time I refresh the Apex wrapper I have had to manually apply the changes to get it working again. This time i invested some time to write a script, written itself in Apex, that will parse the generated code and patch it with the required changes. It uses a customer Iterator to read the lines of code and the Tooling API to update the Apex class when its done.
Working around xsi:type
Once again the use of xsd:extension in the Metadata API proved to be a challenge in getting the readMetadata operation to work. Since as I found before this XML Schema construct is not readily supported by WSDL2Apex or the Apex XML bindings at runtime. The main weakness is the lack of interpretation of the xsi:type attribute, which identifies which Metadata type (e.g. CustomObject, Layout etc) the XML represents. As soon as I tried the readMetadata operation it gave an error as it tried to de-serialise the Layout, into the base Metadata type (which only contains fullName).
To get things to work I had to create some new Apex classes representing the responses from the readMetadata operation for each of the Metadata types. Fortunately the new script i developed can be taught to generate these for me! I’ll eventually post more on the changes to get the readMetadata operation working on the GitHub repo Readme.
Spring’14 is around the corner!
I’ll formally update the library once Spring’14 has been rolled out to the production servers in February. Until then you can download the MetadataService.cls and MetadataServiceExamples.cls classes from this Gist here. You can try it out in your pre-release orgs if you like. I’ll also post a few more fancy examples as well.
Pingback: Apex Metadata API and Spring’14 : Keys to the Kingdom! | Andy in the Cloud
July 30, 2014 at 6:08 am
Hi, Thanks for the detailed information.
But, I am facing an issue in adding the field on page layout and getting the below message when I run the code from “Developer Console “.
EnergyAcuity.MetadataServiceExamples.MetadataServiceExamplesException: Error occured processing component null. Required field is missing: fullName (FIELD_INTEGRITY_EXCEPTION).
Please help me .
July 30, 2014 at 7:26 am
This can happen if the Layout name is not valid. Are you in a namespace org or is the layout a managed one? Take a look at this see if it helps. https://github.com/financialforcedev/apex-mdapi/issues/32
July 30, 2014 at 7:42 am
Thanks alot Andreaw, actually mine is managed package and we need to prefix the name space with layout name also.I am able to add the field on the page layout now
July 30, 2014 at 7:54 am
One more question, will it be possible with Standard object also , say I want to do the same activity on Account Object as well.
I tried below combination but still it failed with same error :
1. MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘Account-EnergyAcuity__Account(Support) Layout’ }).getRecords()[0];
2. MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘Account-Account(Support) Layout’ }).getRecords()[0];
3. MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘EnergyAcuity__Account-EnergyAcuity__Account(Support) Layout’ }).getRecords()[0];
Here EnergyAcuity is namespace and Account(support) is a pagelayout which is not a part of any managed package aswell.
Please let me know your thoughts
August 30, 2014 at 9:03 pm
Andy,
Your blogs and code have really helped me out quite a lot over the past few weeks as we are building out a new managed package. Thank you very much and very sincerely. I do have a question but before I ask, I thought to share a code snippet which creates custom fields **and** adds these to a layout with a known name, e.g. configuration by convention. I did quite a lot of searching on your blogs and google and found piecemeal solutions but perhaps this handy method might help other developers.
private static void addFieldsAndUpdateLayouts(Set newFooBarIDs, String sessionId)
{
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = sessionId;
List bar =
[SELECT Name, ReferenceObjectName__c
FROM FooBar__c
WHERE Id IN :newFooBarIDs];
List addMetas = new List();
List updateMetas = new List();
for(FooBar__c attribute : bar)
{
String fieldApiName = StringUtil.underscoreField(attribute.Name);
// Add lookup to Price Object
MetadataService.CustomField myCustomField = new MetadataService.CustomField();
myCustomField.fullName = 'FooObject__c' + '.' + fieldApiName;
myCustomField.deleteConstraint = 'SetNull';
myCustomField.externalId = false;
myCustomField.label = attribute.Name;
myCustomField.referenceTo = attribute.ReferenceObjectName__c;
myCustomField.relationshipLabel = 'Foo Objects';
myCustomField.relationshipName = 'FooObjects';
myCustomField.required = false;
myCustomField.trackHistory = false;
myCustomField.trackTrending = false;
myCustomField.type_x = 'Lookup';
addMetas.add(myCustomField);
// Read and Update Layout
MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata('Layout',
new String[] { 'FooObject__c-Foo Object Layout' }).getRecords()[0];
for( MetadataService.LayoutSection section : layout.layoutSections)
{
if(section.label == 'Foo Bar') // By Convention
{
if(section.layoutColumns == null)
{
MetadataService.LayoutColumn newLayoutColumn = new MetadataService.LayoutColumn();
section.layoutColumns = new List { newLayoutColumn };
}
if(section.layoutColumns[0].layoutItems == null)
{
section.layoutColumns[0].layoutItems = new List();
}
MetadataService.LayoutItem newLayoutItem = new MetadataService.LayoutItem();
newLayoutItem.field = fieldApiName;
section.layoutColumns[0].layoutItems.add(newLayoutItem);
break;
}
}
updateMetas.add(layout);
}
// TODO - will likely need to synchronize tho I haven't seen a problem yet...lucky
service.createMetadata(addMetas);
service.updateMetadata(updateMetas);
}
August 31, 2014 at 8:54 am
Thanks so much for sharing this! This is what open source is all about! Can I ask that you update the MetadataServiceExamples.cls with this? Using GitHub you can raise a Pull Request and I can merge. Failing that, if you prefer just raise a GitHub Issue and paste to that and I’ll gladly add, thanks for the contribution!
August 30, 2014 at 9:08 pm
Hello Andy,
I am trying to delete custom fields in v31 using code very similar to this example:
MetadataService.MetadataPort service = createService();
List results =
service.deleteMetadata(
‘CustomField’, new String[] { ‘Test__c.TestField__c’ });
handleDeleteResults(results[0]);
But Salesforce is returning the following error:
13:31:45.006 (1006981199)|FATAL_ERROR|System.CalloutException: Web service callout failed: WebService returned a SOAP Fault: null: type and fullNames must be specified for items to delete faultcode=sf:UNKNOWN_EXCEPTION faultactor=
Any idea off the top of your head what might be the problem here?
Thanks,
json
August 31, 2014 at 8:56 am
That is most odd, i’m traveling at the moment, but this looks good. Can you raise for further investigation via the GitHub Issue list and I’ll take a further look probably later today or early next week. Thanks again for your kind words and contribution!
November 17, 2014 at 3:56 pm
What about creating a new layout? Is that possible with Apex?
November 18, 2014 at 6:01 pm
Yes these should be possible. Have you seen the MetadataServiceExamples.cls class in the repo?
December 9, 2014 at 3:51 pm
It seems there is a restriction on creating new layouts based on managed package custom objects. Your otherwise awesome samples work on a new object I created, but failed on a managed package object.
Pingback: Unable to cast type Metadata to type Layout | DL-UAT
April 16, 2015 at 11:58 am
Hi,
Does anybody have an idea why i can’t cast Metadata object to any of it`s subclasses?
I get an exception on this line:
MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘Test__c-Test Layout’ }).getRecords()[0];
I’m using C#. Thanks!
August 24, 2015 at 5:54 am
Hi Andy,
I tried to use readMetadata method to read layout, which give me result without data (means returns null for values). But When I use “Zipping method”, it returns layout data properly. I don’t get why its not working. I tried to use it in another test org and it works fine there.
Can you please suggest why its happening or Am I missing something?
I am using this:
MetadataService.MetadataPort service = createService();
List layouts = (MetadataService.Layout)service.readMetadata(‘Layout’, new String[] {‘Account-Account Layout’}).getRecords(0);
Serialised response:
{“fullName”:null,”type_att_info”:[“xsi:type”],”type”:”Layout”,”summaryLayout_type_info”:[“summaryLayout”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”summaryLayout”:null,”showSubmitAndAttachButton_type_info”:[“showSubmitAndAttachButton”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showSubmitAndAttachButton”:null,”showSolutionSection_type_info”:[“showSolutionSection”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showSolutionSection”:null,”showRunAssignmentRulesCheckbox_type_info”:[“showRunAssignmentRulesCheckbox”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showRunAssignmentRulesCheckbox”:null,”showKnowledgeComponent_type_info”:[“showKnowledgeComponent”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showKnowledgeComponent”:null,”showInteractionLogPanel_type_info”:[“showInteractionLogPanel”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showInteractionLogPanel”:null,”showHighlightsPanel_type_info”:[“showHighlightsPanel”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showHighlightsPanel”:null,”showEmailCheckbox_type_info”:[“showEmailCheckbox”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”showEmailCheckbox”:null,”runAssignmentRulesDefault_type_info”:[“runAssignmentRulesDefault”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”runAssignmentRulesDefault”:null,”relatedObjects_type_info”:[“relatedObjects”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”relatedObjects”:null,”relatedLists_type_info”:[“relatedLists”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”relatedLists”:null,”relatedContent_type_info”:[“relatedContent”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”relatedContent”:null,”quickActionList_type_info”:[“quickActionList”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”quickActionList”:null,”multilineLayoutFields_type_info”:[“multilineLayoutFields”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”multilineLayoutFields”:null,”miniLayout_type_info”:[“miniLayout”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”miniLayout”:null,”layoutSections_type_info”:[“layoutSections”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”layoutSections”:null,”headers_type_info”:[“headers”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”headers”:null,”fullName_type_info”:[“fullName”,”http://www.w3.org/2001/XMLSchema”,”string”,”0″,”1″,”false”],”fullName”:null,”field_order_type_info”:[“fullName”,”customButtons”,”customConsoleComponents”,”emailDefault”,”excludeButtons”,”feedLayout”,”headers”,”layoutSections”,”miniLayout”,”multilineLayoutFields”,”quickActionList”,”relatedContent”,”relatedLists”,”relatedObjects”,”runAssignmentRulesDefault”,”showEmailCheckbox”,”showHighlightsPanel”,”showInteractionLogPanel”,”showKnowledgeComponent”,”showRunAssignmentRulesCheckbox”,”showSolutionSection”,”showSubmitAndAttachButton”,”summaryLayout”],”feedLayout_type_info”:[“feedLayout”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”feedLayout”:null,”excludeButtons_type_info”:[“excludeButtons”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”excludeButtons”:null,”emailDefault_type_info”:[“emailDefault”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”emailDefault”:null,”customConsoleComponents_type_info”:[“customConsoleComponents”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”1″,”false”],”customConsoleComponents”:null,”customButtons_type_info”:[“customButtons”,”http://soap.sforce.com/2006/04/metadata”,null,”0″,”-1″,”false”],”customButtons”:null,”apex_schema_type_info”:[“http://soap.sforce.com/2006/04/metadata”,”true”,”false”]}
August 24, 2015 at 7:16 am
These can be tricky, especially for managed layouts. Have you read my faq blog? There is some tips in that.
August 24, 2015 at 8:33 am
Hi Andy,
I resolved it, Issue was occurring due to namespace that needs to be added in Layout or Object Name.
Thanks
August 24, 2015 at 9:58 am
Great news thanks
September 9, 2015 at 4:33 am
Hi Andy,
I am not able to figure out field api name from alias as Metadata api return field alias instead of api name while reading fields in related list of page layout using readMetadata().
Can you please suggest a way by which field api name can be figured out from alias?
Thanks
September 30, 2015 at 11:48 am
Can yuo please tell me how to delete roles using metadata api.
Right now I am getting an error saying that type is not a valid datatype for metadata to delete.
Web service callout failed: WebService returned a SOAP Fault: null: type is not a valid metadata type for deleting faultcode=sf:UNKNOWN_EXCEPTION faultactor=
September 30, 2015 at 12:06 pm
Check Slaesforce metatada API docs, it may not be supported
May 30, 2016 at 12:02 pm
With the help of below code I am trying to update layout like remove related list. After using NameSpace in my ORG, I am facing this problem, earlier I were not using NameSpace in my ORG than code was running perfectly.
I have a Fund__c object as a Parent and Fundraising__c as a child object. In my related list Fundraising__c I added custom field Legal_Name__c as a column. If I remove this custom field, code run perfectly. But with namespace its not working throwing error.
Please provide me solution its urgent.
Thanks in advance.
Please find the code below.
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
MetadataService.Layout layout = (MetadataService.Layout)service.readMetadata(‘Layout’,new String[] {‘navpeIIll__Fund__c-navpeIIll__Fund Layout’}).getRecords()[0];
system.debug(‘layout===========’+layout);
List lstMD= layout.relatedLists;
system.debug(‘lstMD===========’+lstMD);
for(integer i=0;i<lstMD.size();i++){
MetadataService.RelatedListItem mdsR= lstMD[i];
system.debug('lstMD==========='+mdsR);
if(mdsR.relatedList== 'navpeIIll__Fundraising__c.navpeIIll__Fund__c'){
layout.relatedLists.remove(i);
system.debug('layout====50======='+layout);
}
}
MetadataServiceExamples.handleSaveResults(service.updateMetadata(new MetadataService.Metadata[] { layout })[0]);
May 30, 2016 at 4:47 pm
Thanks for raising on GitHub, will take a look now
December 18, 2019 at 3:25 am
Hi Andy, Is there a way to get the name of the layout based on logged in user and record type?
In above example, we hard code name of the layout to get it’s metadata information.
MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘Test__c-Test Layout’ }).getRecords()[0];
My use case is to read logged in user’s standard detail component of lightning record page and use this to recreate custom detail component.
December 28, 2019 at 7:48 pm
I am curious why you want to recreate the standard detail layout tbh but I know your not asking that. But still check out the Lightning components that help you render this and customize that layout such as lightning:inputField. Meanwhile check out the Salesforce User Interface API or in Apex you can get this via a Apex Describe. 👍🏻