The Apex Metadata API has now been updated to reflect the Metadata API from Winter’15, which means a host of new component types, including the topic on everyones lips, Lightning! Though sadly, my demo around this will have to wait as I hit a platform issue i’ve sent to Salesforce Support to help me with.
This library continues to be incredibly popular, i can get between 1 to 4 questions a week asking how to accomplish certain goals, via my blog and/or issues raised on the GitHub repository. So I’m really pleased to continue to support it and keep it fresh! One day we will get native support for this, but until that day I’ll press on….
A lot of questions i get are around the Apex Metadata API, though almost all are really about using the Metadata API itself regardless of the calling language. So i thought i’d focus this blog on sharing some of the ways i’ve tackled unlocking the secrets of the mighty Metadata API. While the following tips and tricks are given in the context of Apex, some will work just fine in other languages consuming this API.
1. Do I have to download the Metadata API WSDL?
You don’t, just use the MetadataService.cls and its MetadataServiceTest.cls directly in your org. The point of this library is to avoid you having to go through the process of creating and manipulating the generated code from the Salesforce WSDL to Apex tool (the output of which is not directly useable). So unless you find i’m slacking and have not updated the library to the latest version of the platform, your good to go with the latest one in the repository!
2. How do I create X using the Metadata API?
The Apex Metadata API consists of many hundreds of classes within the MetadataService.cls file. Some represent top level Metadata components (items in your org), such as CustomObject, some are child components such as CustomField, others are simply class types referenced by these. Knowing which is which is important to know where to start, as only those classes that represent Metadata components can be used with the CRUD operations.
The easiest way to determine this is to first open the Metadata API documentation and look at the list of components here. The documentation also has a really good topic on how to spot the fact that classes will extend Metadata, if so they represent Metadata component classes you can use with the CRUD operations. As the topic suggests, despite the library having already used the WSDL, it is still worth downloading for reference purposes. While I try to make the Apex code match this extension concept it may not always be possible (Folders are a current gap).
So for example we see that Layout looks like this in the WSDL….
<xsd:complexType name="Layout"> <xsd:complexContent> <xsd:extension base="tns:Metadata"> <xsd:sequence> <xsd:element name="customButtons" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/> <xsd:element name="customConsoleComponents" minOccurs="0" type="tns:CustomConsoleComponents"/> <xsd:element name="emailDefault" minOccurs="0" type="xsd:boolean"/> <xsd:element name="excludeButtons" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/> <xsd:element name="feedLayout" minOccurs="0" type="tns:FeedLayout"/> <xsd:element name="headers" minOccurs="0" maxOccurs="unbounded" type="tns:LayoutHeader"/> <xsd:element name="layoutSections" minOccurs="0" maxOccurs="unbounded" type="tns:LayoutSection"/> <xsd:element name="miniLayout" minOccurs="0" type="tns:MiniLayout"/> <xsd:element name="multilineLayoutFields" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/> <xsd:element name="quickActionList" minOccurs="0" type="tns:QuickActionList"/> <xsd:element name="relatedContent" minOccurs="0" type="tns:RelatedContent"/> <xsd:element name="relatedLists" minOccurs="0" maxOccurs="unbounded" type="tns:RelatedListItem"/> <xsd:element name="relatedObjects" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/> <xsd:element name="runAssignmentRulesDefault" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showEmailCheckbox" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showHighlightsPanel" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showInteractionLogPanel" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showKnowledgeComponent" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showRunAssignmentRulesCheckbox" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showSolutionSection" minOccurs="0" type="xsd:boolean"/> <xsd:element name="showSubmitAndAttachButton" minOccurs="0" type="xsd:boolean"/> <xsd:element name="summaryLayout" minOccurs="0" type="tns:SummaryLayout"/> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType>
And looks like this in MetadataService.cls…
public class Layout extends Metadata { public String type = 'Layout'; public String fullName; public String[] customButtons; public MetadataService.CustomConsoleComponents customConsoleComponents; public Boolean emailDefault; public String[] excludeButtons; public MetadataService.FeedLayout feedLayout; public String[] headers; public MetadataService.LayoutSection[] layoutSections; public MetadataService.MiniLayout miniLayout; public String[] multilineLayoutFields; public MetadataService.QuickActionList quickActionList; public MetadataService.RelatedContent relatedContent; public MetadataService.RelatedListItem[] relatedLists; public String[] relatedObjects; public Boolean runAssignmentRulesDefault; public Boolean showEmailCheckbox; public Boolean showHighlightsPanel; public Boolean showInteractionLogPanel; public Boolean showKnowledgeComponent; public Boolean showRunAssignmentRulesCheckbox; public Boolean showSolutionSection; public Boolean showSubmitAndAttachButton; public MetadataService.SummaryLayout summaryLayout; }
Next find the page for this Metadata type in the Metadata API documentation, in this case Layout is here. Force.com is a complex platform at times, and the number of fields can be quite bewildering. So what i usual do is one or both of the following tricks.
Firstly, when you scroll down to the bottom of the documentation topics, there is usually an example of the component in XML form. Secondly I sometimes use my favourite tool, Force.com or MavensMate to download the component in file form from my org to generate my own example, useful if the documentation example is to basic for your needs. Basically the XML form in either case, matches exactly the data structures and values you need to provide in Apex.
For example the Layout example looks like this…
<Layout xmlns="http://soap.sforce.com/2006/04/metadata"> <layoutSections> <editHeading>true</editHeading> <label>System Information</label> <layoutColumns> <layoutItems> <behavior>Readonly</behavior> <field>CreatedById</field> </layoutItems> <layoutItems> <behavior>Required</behavior> <field>Name</field> </layoutItems> </layoutColumns> <layoutColumns> <layoutItems> <behavior>Readonly</behavior> <field>LastModifiedById</field> </layoutItems> </layoutColumns> <style>TwoColumnsTopToBottom</style> </layoutSections> <summaryLayout> <masterLabel>Great Name</masterLabel> <sizeX>4</sizeX> <sizeY>2</sizeY> <summaryLayoutItems> <posX>0</posX> <posY>0</posY> <field>Name</field> </summaryLayoutItems> </summaryLayout> </Layout>
Note you will need to set the fullName field as well, to understand how to format this for your Metadata component refer to the following Q&A items for more discussion. The equivalent Apex code would look like this…
MetadataService.Layout layout = new MetadataService.Layout(); layout.fullName = 'Test__c-My Layout'; layout.layoutSections = new List<MetadataService.LayoutSection>(); MetadataService.LayoutSection layoutSection = new MetadataService.LayoutSection(); layoutSection.editHeading = true; layoutSection.label = 'System Information'; layoutSection.style = 'TwoColumnsTopToBottom'; layoutSection.layoutColumns = new List<MetadataService.LayoutColumn>(); MetadataService.LayoutColumn layoutColumn = new MetadataService.LayoutColumn(); layoutColumn.layoutItems = new List<MetadataService.LayoutItem>(); MetadataService.LayoutItem layoutItem1 = new MetadataService.LayoutItem(); layoutItem1.behavior = 'Readonly'; layoutItem1.field = 'CreatedById'; layoutColumn.layoutItems.add(layoutItem1); MetadataService.LayoutItem layoutItem2 = new MetadataService.LayoutItem(); layoutItem2.behavior = 'Required'; layoutItem2.field = 'Name'; layoutColumn.layoutItems.add(layoutItem2); layoutSection.layoutColumns.add(layoutColumn); layout.layoutSections.add(layoutSection); layout.summaryLayout = new MetadataService.SummaryLayout(); layout.summaryLayout.masterLabel = 'Great name'; layout.summaryLayout.sizeX = 4; layout.summaryLayout.sizeY = 2; layout.summaryLayout.summaryLayoutStyle = 'Default'; layout.summaryLayout.summaryLayoutItems = new List<MetadataService.SummaryLayoutItem>(); MetadataService.SummaryLayoutItem summaryLayoutItem = new MetadataService.SummaryLayoutItem(); summaryLayoutItem.posX = 0; summaryLayoutItem.posY = 0; summaryLayoutItem.field = 'Name'; layout.summaryLayout.summaryLayoutItems.add(summaryLayoutItem); List<MetadataService.SaveResult> results = service.createMetadata( new MetadataService.Metadata[] { layout });
Note that this is a complex class with many other class types needed to expressed sections etc for the layout. All of these classes will be in the MetadataService.cls class as inner classes. Have this file open as you work your way through mapping the XML structure to the Apex one, so that you can see the field names and their Apex class types. Alternatively you can also have the WSDL open, as the names of the schema types will also match those in Apex.
3. How do I update X using the Metadata API?
Unlike records, you don’t always have to have read the Metadata component in order to update it. Nor do have to complete all the fields, just the ones you want to update. For example the following will update the labels on a Custom Object, but will not for example result in the Description, Custom Help or other Custom Object information being cleared just because you didn’t set them. Quite handy!
MetadataService.CustomObject customObject = new MetadataService.CustomObject(); customObject.fullName = 'Test__c'; customObject.pluralLabel = 'Update Labels'; customObject.nameField = new MetadataService.CustomField(); customObject.nameField.type_x = 'Text'; customObject.nameField.label = 'Test Record Upsert'; customObject.deploymentStatus = 'Deployed'; customObject.sharingModel = 'ReadWrite'; List<MetadataService.UpsertResult> results = service.upsertMetadata( new MetadataService.Metadata[] { customObject });
As always though, there are some exceptions to this rule, which are sadly not documented. Fortunately if you’ve not set certain fields required for an update (and some don’t often make sense) the API is pretty good at telling you which ones you need. At this point however your probably going to want to know what the existing field values are, thus you will need to read the Metadata component, set the fields your wanting to update and then update it. You will of course want read a Metadata component if you want to add to a list of things, such as a PickList.
// Read Custom Field MetadataService.CustomField customField = (MetadataService.CustomField) service.readMetadata('CustomField', new String[] { 'Lead.picklist__c' }).getRecords()[0]; // Add pick list values metadataservice.PicklistValue two = new metadataservice.PicklistValue(); two.fullName= 'second'; two.default_x=false; metadataservice.PicklistValue three = new metadataservice.PicklistValue(); three.fullName= 'third'; three.default_x=false; customField.picklist.picklistValues.add(two); customField.picklist.picklistValues.add(three); // Update Custom Field handleSaveResults( service.updateMetadata( new MetadataService.Metadata[] { customField })[0]);
4. How do I know the Full Name of the Metadata Component?
Metadata components are not referenced using Id’s, they use their Full Name. This can be a little tricky to track down sometimes, especially when it is a component such as a Layout that arrived in the org via a managed package, which means the Full Name will likely need to include the applicable namespace qualification, in some cases more than once!
The first thing i do is use the Developer Workbench to confirm the Full Name.
MetadataService.Report report = (MetadataService.Report) service.readMetadata('Report', new String[] { 'MyFolder/MyReport' }).getRecords()[0];
As one of the users of the API found out, this can sometimes not give you an accurate result however. So the one known gotcha being Layouts, where the Full Name needs to be qualified twice with the Namespace. For example to retrieve a Layout installed with my DLRS managed package the following code would be required.
Note the dlrs namespace prefix is applied to both the Custom Object name portion of the Layout name, as well as the Layout name itself. In this case i still used the Developer Workbench approach above to get most of the name, then applied this now known adjustment to get it to work. The follow reads a packaged layout.
MetadataService.Layout layout = (MetadataService.Layout) service.readMetadata('Layout', new String[] { 'dlrs__LookupRollupSummaryLog__c-dlrs__Lookup Rollup Summary Log Layout' }).getRecords()[0];
5. Why does readMetadata not thrown an exception?
The previous examples have been using the readMetadata operation. This operation will not throw any exception if any of the full names given to it are invalid (as in the custom object does not exist). Instead empty records will be returned. For example the following illustrates that even with an invalid custom object Full Name a Metadata record is still returned, but the Full Name is null. So the advice here is to check the FullName for being none null before you trust the contents of information returned from readMetadata…
MetadataService.CustomObject customObject = (MetadataService.CustomObject) service.readMetadata('CustomObject', new String[] { 'DoesNotExist__c' }).getRecords()[0]; System.assertEquals(customObject.FullName, null);
6. Error ‘x’ is not a valid value for the enum ‘y’
While the Metadata API WSDL does contain a list of valid values for fields, the generated Apex code does not result in Enum’s being created for them sadly. As such you have to know the correct String value to set. This can often be determined from the documentation of course (though i have found a few typos in the docs in the past). So if your still struggling, check the WSDL enumeration to confirm you’ve got the correct value.
For example in the WSDL…
<xsd:complexType name="WebLink"> <xsd:complexContent> <xsd:extension base="tns:Metadata"> <xsd:sequence> ... <xsd:element name="displayType" type="tns:WebLinkDisplayType"/> ... </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:simpleType name="WebLinkDisplayType"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="link"/> <xsd:enumeration value="button"/> <xsd:enumeration value="massActionButton"/> </xsd:restriction> </xsd:simpleType>
Then in Apex code…
MetadataService.WebLink webLink = new MetadataService.WebLink(); webLink.fullName = 'Test__c.googleButton'; webLink.availability = 'online'; webLink.displayType = 'button';
7. How can I create or update Apex code?
The only way to create or update an Apex class or Trigger is to use the deploy operation, take a look at the Deploy demo in the library here for more information. I have also used this approach in my DLRS tool here. You can also use the Tooling API if you desire, however this only works in DE orgs, it cannot be used in a Production org. Currently the only way i know to create an Apex class or Trigger in Production is to use the deploy operation.
8. Retrieving Profile or Permission Set Information
You can retrieve a PermissionSet using the readMetadata as follows…
MetadataService.MetadataPort service = createService(); MetadataService.PermissionSet ps = (MetadataService.PermissionSet) service.readMetadata('PermissionSet', new String[] { 'Test' }).getRecords()[0];
The following code reads the Administrator Profile.
MetadataService.MetadataPort service = createService(); MetadataService.Profile admin = (MetadataService.Profile) service.readMetadata('Profile', new String[] { 'Admin' }).getRecords()[0];
Finally, also worth considering if your querying only, is that you can use SOQL to query the Permission Set objects directly, scroll down to the bottom of this topic to see the object model diagram.
Summary
I hope these will help future Metadata API coders with some of the more obscure behaviours and tasks they need to perform. You can also read more about the Metadata API operations here. If you’ve read all this and still find yourself scratching your head please feel free to raise an issue on the GitHub repository here and I’ll try to help!
November 23, 2014 at 5:35 pm
Reblogged this on SutoCom Solutions.
November 24, 2014 at 8:03 pm
Awesomeness!
November 25, 2014 at 11:16 pm
Thanks Andy. Again a great article. Does nee version solve the problem of “having the instance as remote site” before calling the metadata api?
I need to create fieldsets on package install. I tried using MetaData API in the past, but it would not work unless there was remote site to same Salesforce instance to which the app is being installed, for e.g. na11.salesforce.com. This did not make sense, as it was invoking from same org.
Do I still have to worry about this?
November 27, 2014 at 6:17 pm
I’m afraid not, you still need to refer to my other blog on how to semi-automate this. But sadly no solution from package install script, best is a post install VF page.
May 2, 2019 at 11:16 pm
Hi Andy,
I’m trying to automate the creation of Remote Site Settings as well, and I was wondering if you could direct me to your blog entry that “semi-automates” this. My current approach is to exec a VF page, but alas, I still get the
IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = [mydomain].my.visualforce.com/services/Soap/m/42.0
My next approach is to publish a REST API resource (which in turn calls the mdapi) that I will call from an action method in the VF Page. I’m hoping that since the REST call will be outside of the “.visualforce.com” context, the Winter 19 changes will allow the call without adding a Remote Site Setting.
Thanks very much for any guidance,
What you’ve done with this is amazing.
May 9, 2019 at 11:23 pm
Sure. https://andyinthecloud.com/2014/07/29/post-install-apex-metadata-api-configuration-solved/
December 12, 2014 at 6:12 pm
Thanks Andrew. Can you throw in quick example on how I could retrieve an element, let’s say a FieldSet? I would like to create an element only if it does not exist already.
December 16, 2014 at 10:18 am
There are quite a few examples in the MetadataServiceExamples.cls class (in the repo also) have you checked that out? If so, and your need something more, please raise a GitHub issue and i’ll see what i can do for you! 🙂
December 16, 2014 at 8:59 am
Thanks Andy. Can we write the metadata of a Standard Field using the MetaData API?. Particularly the help text of Standard Fields. This can be done by Standard Salesforce screens, but I am not able to figure out how to do it from the API layer
December 16, 2014 at 10:22 am
Yes you can do this, the help text is exposed as a member variable on the CustomField class. Take a look at the Custom Field examples in the MetadataServiceExamples.cls (also in the repo). I think there is one that updates a Picklist field, you should be able to see what needs doing from that. Let me know via a GitHub issue on the repo if your still needing some help after taking a look at this, and i’d be happy to help more! 🙂
December 17, 2014 at 11:00 pm
Andy, do you know if we can create custom formula fields via metadata? I looked the class and field types here: https://www.salesforce.com/us/developer/docs/api_meta/Content/customfield.htm
I do not think it is possible. Can you please confirm?
December 18, 2014 at 8:25 am
Yes that should be possible, here is a basic Custom Field creation example, you should be able to adapt it to fill in the formula field example. https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls#L117. As i mention in the Q&A if your unsure what fields to set, create one manually and pull down the .object file to inspect what Salesforce has set to guide you.
December 18, 2014 at 3:02 pm
Andy, I did try that yesterday. Salesforce will not allow customField.type_x = ‘Formula’; Hence I wanted to confirm it.
All the field types are mentioned here and I do not see formula field https://www.salesforce.com/us/developer/docs/api_meta/Content/customfield.htm
December 22, 2014 at 1:47 pm
Its not a field type, more an attribute of the field definition as far as i can see, i’ll put together a sample now…
December 22, 2014 at 1:52 pm
Here you go, https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls#L987
December 22, 2014 at 4:56 pm
Thanks Andy. That worked like a charm. Let me know if you have happen to be in Austin area.. beer is on me.
December 22, 2014 at 5:04 pm
Great news and thanks i will! 🙂
December 23, 2014 at 4:27 pm
Declared victory too soon. While I am able to create custom field via Metadata, it turns out I cannot do FLS with such ease. I have to manually set FLS for all profiles. My uses case is simple, all profiles should have read access to it.
I found this old thread on how you can set FLS using code, this is like killing fly with bazooka. Is there any better way to achieve it? Here is the link: http://salesforce.stackexchange.com/questions/34521/set-field-level-security-for-profile-using-meta-data-api
December 23, 2014 at 4:38 pm
You don’t need to use deploy/retrieve you can use the CRUD methods. Have you seen this example? https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls#L154 You can also create a Permission Set in Apex code directly using DML and also assign it to your users, again in code. I’d personally recommend this over adapting profiles, but either approach here should do what you want. Let me know if you want any more details. 🙂
December 23, 2014 at 4:48 pm
That was fast! I do not like to create permission set if I don;t have to. Thank you for the example, looks like it would still require to get all profiles in for loop and update FLS for each. Is there global setting where we can set same FLS for all profile at once?
December 23, 2014 at 4:55 pm
Yes that would be a single shared Permission Set assigned to all your users. 😉
December 23, 2014 at 7:49 pm
Made some progress but getting exception with below code if there more than 10 profiles. Hence I restricted to just system admin profiles, now I strange error message in debug log. Can you please tell me what am I missing here? Thank you.
//Store all profile names in list variable
list profileNames = new list();
for(Profile profile : [SELECT Id, Name FROM Profile WHERE UserType=’Standard’ AND Name LIKE ‘System%’ ORDER BY Name]){
profileNames.add(profile.Name);
}
//Get the field
MetadataService.ProfileFieldLevelSecurity fieldSec = new MetadataService.ProfileFieldLevelSecurity();
fieldSec.field = ‘custom_object__c.custom_field__c’;
fieldSec.readable = true;
fieldSec.editable = true;
//Update FLS for each profile
for(MetadataService.Metadata metadata : service.readMetadata(‘Profile’, profileNames).getRecords()){
MetadataService.Profile profile = (MetadataService.Profile)metadata;
profile.fieldPermissions = new MetadataService.ProfileFieldLevelSecurity[] {fieldSec};
updateMetadata.add(profile);
}
//Call metadata service
if(updateMetadata.size()>0 && UserInfo.getOrganizationId() != ’00Dd0000000cDqREAU’)
service.updateMetadata(updateMetadata);
// ERROR MESSAGE
Web service callout failed: WebService returned a SOAP Fault: UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1896081984-142957 (-1781941849) faultcode=sf:UNKNOWN_EXCEPTION faultactor=
December 23, 2014 at 8:44 pm
I’m not able to decode those types of errors, only Salesforce support can do that. As the message suggests you have to raise a support case and provide your org id and this error message. They usual then tell you the underlying error message or declare it a platform API bug (which is basically what it is, since it’s not giving you a friendly error message).
December 28, 2014 at 4:48 pm
Sorry Andrew. I did not mean that. Only Salesforce can help debug such user-friendly messages. I was asking from logic standpoint. Do you see anything wrong as I have very limited knowledge working with metadata API.
March 10, 2015 at 8:46 am
Hi Andrew,
I just found your MetadataServices class today (the one here https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataService.cls). I copied the class, test class, and examples class in to my salesforce developer org (along with all the other classes just to be on the safe side) and created a remote site setting for https://na16.salesforce.com (the URL of my dev org). When I try to run any of your examples however (like the customObject example), I get the following error, “Method does not exist or incorrect signature: createService()”. What’s weird though, is that I can’t seem to find the code that calls this method in the MetadataServices class. My org is API 33.0, which I know is the Spring 15 version, but I can’t figure out why the createService() method seems to be missing from the class. Do you have any ideas on why it might be throwing that error or what I might be doing wrong? Any insight would be seriously appreciated.
Thanks!
March 10, 2015 at 10:42 pm
That’s odd, this method is only in MetadataServiceExamples.cls
March 12, 2015 at 2:22 am
Hi Andrew, how would you recommend that I adjust my execution of your examples? I’m admittedly a bit of a newb to this area and played with it for about 4 hours on Tuesday before finally throwing my hands up and reaching out to you. Just deleting that section of the example methods (the createService part) doesn’t seem to fix the problem (as it just throws a different error at that point). I’m not quite sure what I need to adjust. Thanks a bunch for your response btw.
March 12, 2015 at 7:45 am
No worries, can you post on the github repo as an issue with a sample of your code and I will try to help there. Thanks
March 13, 2015 at 5:16 am
Done. Thanks Andrew! Talk to you on there.
February 21, 2017 at 10:26 am
Is there any solution for this issue “Method does not exist or incorrect signature: createService()”?
February 21, 2017 at 8:24 pm
Yes create the method, you can copy it from the MetadataServiceExamples.cls
March 27, 2015 at 12:20 pm
Hi Andy,
The code at github helped me a lot and I’m able to achieve most of the required things via Metadata but stuck at sharing rule creation.
I have created new MetadataService class using API version 33.0 specially to create sharing rule using Metadata API and when I try to create sharing rule, I receive either no error or unknown error from salesforce on callout.
I posted my question on stack exchange:
http://salesforce.stackexchange.com/questions/69977/issue-with-creating-sharing-rule-using-metadata-api-version-33-0
If you could take a look and let know what I’m doing wrong.
March 27, 2015 at 1:56 pm
This is currently not working, but will once the library is updated to spring 15
March 27, 2015 at 2:33 pm
But I’m using spring’15 version 33.0 Metadata API WSDL only.
March 27, 2015 at 8:02 pm
The generated one doesn’t work either, I need to release a new modified version via the github repo
March 27, 2015 at 5:57 pm
Hey Andrew,
First of all, the APEX Metadata API you have created is very helpful and an amazing piece of code. Thank you for taking the time to do this.
I am trying to see if I can use the MetadataService class you have created to deploy Metadata to other orgs that I have access to. I know it is possible to use APEX to deploy to other orgs as http://www.flosum.com/ already does this very well.
However, I am trying to just understand how I would go about achieving this using the class you have created.
Any help or tips you could give me would be extremely appreciated.
March 27, 2015 at 8:03 pm
Set the sessionId to an oAuth token obtained via oAuth web flow
April 1, 2015 at 10:11 am
Hi Andrew, I am trying to retrieve all the Page Layouts of an Object without having hard-coded Layout names in my MataData API calls from Apex but did find a way to achieve that. Are there any methods available in the API which can be used to pull all Layout names for an Object? Thanks you in Advance.
April 1, 2015 at 12:36 pm
You could try the listMetadata methid
April 2, 2015 at 3:35 pm
Hi ALL
i am getting below mentioned error when try to use META API apex class
Compile error at line 15 column 41 Method does not exist or incorrect signature: [MetadataService.MetadataPort].create(List)
i am using Eclipse 3.6 Force.com IDE.
Regards
Sumit
April 6, 2015 at 12:30 pm
Have you compared your code with that in the MetadataServiceExamples.cls file?
April 9, 2015 at 10:05 am
Thanks for your reply, now it is working.
April 9, 2015 at 7:52 pm
Welcome!
April 3, 2015 at 6:50 pm
Hi Andrew,
I was unable to retrieve all page layouts in one org. I am using this code but response is null.
// Read the Layout
MetadataService.MetadataPort service = createService();
system.debug( service.readMetadata(‘Layout’,
new String[] { ‘*’ }).getRecords());
Please suggest
April 4, 2015 at 12:06 pm
You cannot pass wildcards to this method. You must either use listMetadata method and then call retrieveMetadata or use the retrieve method. You can see examples in the github repo and also don’t forget the salesforce Metadada api docs.
April 20, 2015 at 5:02 am
Hi Andrew ,
I am facing a weird issue regarding update layout. My code is working fine unless its an unmanaged packaged but as soon as I shift it into managed package it starts throwing unknown exception.
Error like this: Web service callout failed: WebService returned a SOAP Fault: UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 697030706-2552 (-407299373) faultcode=sf:UNKNOWN_EXCEPTION faultactor=3058
and I am working with standard objects only although I tried adding namespace too but not working. Using code like this : MetadataService.Layout layout =
(MetadataService.Layout) service.readMetadata(‘Layout’,
new String[] { ‘Account-dragNdrop__Account-Account Layout’ }).getRecords()[0];
handleSaveResults(
service.updateMetadata(
new MetadataService.Metadata[] { layout })[0]);
Let me know what you think.
April 20, 2015 at 7:18 am
Layouts and managed packages are tricky for sure, worth scanning some of the github issues in the repo for tips, also check my faq blog post to see how to use developer workbench to confirm the name
October 20, 2015 at 6:50 am
Hello Andy, Great Article, thanks. Is there a way of adding a VF page to a standard layout in lightning experience using the meta data api?
October 20, 2015 at 7:11 am
You should be able to retrieve the layout, edit it in memory to add the section and item for the page then update it. Take a look at the sample code in the repo and also retrieve via xml a layout to see how it’s setup and replicate in your code
November 13, 2015 at 7:19 pm
Hello Andy,
I truly appreciate all your efforts in this area. I’ve been able to use your code as the basis for automating the creation of objects and fields in SF.
I am puzzled by the “hard-coded” element of your MetadataServiceExamples.cls – is there a reason why you took this approach as opposed to designing the createObject method to be called with parameters? e.g. something akin to the following:
public static void createObject( string objFullName
, string objLabel
, string objPluralLabel
, string objNameFieldTypeX
, string objNameFieldLabel
, string objDescription
, string objDeploymentStatus
, string objSharingModel
, boolean objEnableReports
, boolean objEnableActivities
, boolean objEnableHistory
, boolean objEnableFeeds
, boolean objEnableBSS // Allow Sharing, Allow Bulk API Access, and Allow Streaming API Access must be enabled together or disabled together
, boolean objEnableDivisions
, boolean objEnableEnhancedLookup
)
{
MetadataService.MetadataPort service = createService();
MetadataService.CustomObject customObject = new MetadataService.CustomObject();
customObject.fullName = objFullName;
customObject.label = objLabel;
customObject.pluralLabel = objPluralLabel;
customObject.nameField = new MetadataService.CustomField();
customObject.nameField.type_x = objNameFieldTypeX;
customObject.nameField.label = objNameFieldLabel;
customObject.description = objDescription;
customObject.deploymentStatus = objDeploymentStatus;
customObject.sharingModel = objSharingModel;
customObject.enableReports = objEnableReports;
customObject.enableActivities = objEnableActivities;
customObject.enableHistory = objEnableHistory;
customObject.enableFeeds = objEnableFeeds;
customObject.enableBulkApi = objEnableBSS;
customObject.enableSharing = objEnableBSS;
customObject.enableStreamingApi = objEnableBSS;
customObject.enableDivisions = objEnableDivisions;
customObject.enableEnhancedLookup = objEnableEnhancedLookup;
List results =
service.createMetadata(
new MetadataService.Metadata[] { customObject });
handleSaveResults(results[0]);
}
November 13, 2015 at 7:32 pm
This is by design, which is to mirror Salesforce Metadata API, the MetadataService class is a most system generated wrapper for.
January 18, 2016 at 6:10 pm
Hi Andy,
Can we minimize the metadataservice class to have only required metadata helper classes?
January 19, 2016 at 6:51 pm
Only by manually deleting things or copying over incrementally to a new class the things you need. It is an enhancement to provide a better way to do this.
March 9, 2016 at 10:06 am
Great Article @Andy
April 22, 2016 at 8:43 pm
Hi Andy,
Is it possible to fetch other org’s metadata?
June 27, 2016 at 7:41 pm
Not able to retrieve Apex class. Code which I am using this :
MetadataService.MetadataPort service = MetadataServiceExamples.createService();
system.debug(service.readMetadata(‘ApexClass’,new String[] {‘StringArrayTest’}).getrecords()[0]);
Error I am getting is this :
00:59:29:268 FATAL_ERROR System.CalloutException: Web service callout failed: WebService returned a SOAP Fault: INVALID_TYPE: This type of metadata is not available for this organization faultcode=sf:INVALID_TYPE faultactor=
June 28, 2016 at 6:27 pm
This is a Salesforce API restriction, nothing I can do about it. However see my other comment response for options.
June 27, 2016 at 10:01 pm
Can we retrieve apex class content ? I am not able to do so
June 28, 2016 at 6:27 pm
Yes with the retrieve API, as per the Retrieve Demo in the GitHub repo. Or a better way is to just use SOQL on the ApexClass object (see soap developers guide), you can query the Body field. You can only read records on this object. To update code, you need to use the deploy metadata API, again there is a deploy example in the GirHub repo.
July 12, 2016 at 3:26 am
Andy, can we retrieve Global Pick-list through this?
July 12, 2016 at 6:12 am
It was recently updated to the API version to support this. Though I have yet to physically try it. In theory it should I would say. Let me know how you get on. It would be great to add a sample to the metadataserviceexamples class. 🙂
July 12, 2016 at 7:09 am
Thanks for Reply, I tried it for Global Pick list by using this code :
MetadataService.GlobalPickList report = (MetadataService.GlobalPickList) service.readMetadata(‘GlobalPickList’, new String[] { ‘TestGlobal’ }).getRecords()[0];
But it throws error “Web service callout failed: WebService returned a SOAP Fault: null: type is not a valid metadata type for reading faultcode=sf:UNKNOWN_EXCEPTION faultactor= “.
Can you please confirm me that we can’t access global pick-list values directly in Apex?
July 13, 2016 at 7:59 am
This looks like a restriction of the Metadata API. The docs don’t appear to indicate one way or another… https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_globalpicklist.htm
July 13, 2016 at 8:00 am
If you raise an issue on the GitHub site I will investigate further for you at some point
October 20, 2016 at 6:37 pm
Hi Andrew.
This is was an amazing post. I tried to run it in my org and although i am able to get the results, they are not as expected. I am trying to get a list of Names of all the workflows which are present in my org, i use this code :
public static void listMetadata()
{
MetadataService.MetadataPort service = createService();
List queries = new List();
MetadataService.ListMetadataQuery queryWorkflow = new MetadataService.ListMetadataQuery();
queryWorkflow.type_x = ‘Workflow’;
queries.add(queryWorkflow);
//MetadataService.ListMetadataQuery queryValidationRule = new MetadataService.ListMetadataQuery();
//queryValidationRule.type_x = ‘ValidationRule’;
//queries.add(queryValidationRule);
MetadataService.FileProperties[] fileProperties = service.listMetadata(queries, 25);
for(MetadataService.FileProperties fileProperty : fileProperties)
System.debug(‘********The Names are :’+fileProperty.fullName);
}
public static MetadataService.MetadataPort createService()
{
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
return service;
}
listMetadata();
But i do not get the names of the workflows, instead a list of objects. What am i doing wrong here?
October 21, 2016 at 5:12 pm
Nothing, workflows are bound to objects, thus this is a list of objects that have workflows assigned.
May 9, 2017 at 5:50 am
CustomField is always NULL.
Can anyone suggest a resolution for this common code snippet below? The field exists and I can create/update using the Apex Metadata API just fine.
// Read Custom Field MetadataService.CustomField customField = (MetadataService.CustomField) service.readMetadata( ’CustomField’, new String[] { ‘Account.CustomerPriority__c’ } ).getRecords()[0];
Ultimately, I want to create pick list values and activate them on record types. I see I need to update all the existing pick list values otherwise they will be deactivated on the record type.
May 15, 2017 at 12:25 am
Hmmm I have a feeling you can only read by retrieving the object. Though I can only find a reference to the fact in the retrieve API topic not the readmetadata one.
May 11, 2017 at 9:01 pm
Hi Andy,
Sorry, I posted this on the wrong thread before, and could really use your assistance with this wonderful api.
Can you consider why I get nulls for all properties reading any object or element.
Even the simple snippet below returns all nulls. Creating is PERFECT and error free!
MetadataService.CustomField customField = (MetadataService.CustomField) service.readMetadata(‘CustomField’, new String[] { ‘Lead.picklist__c’ }).getRecords()[0];
Thank you
May 23, 2017 at 10:26 pm
Just responded on your other message.
May 23, 2017 at 10:28 pm
I got confirmation, you cannot retrieve specific fields, you have to retrieve entire object
June 17, 2017 at 1:54 am
Trying to add data to picklist and here is the error:
Error is in expression ‘{!saveNew}’ in component in page dynamiclist: Class.MetadataService.MetadataPort.upsertMetadata: line 11599, column 1
Class.DynamicListController.addPicklistValue: line 119, column 1
June 17, 2017 at 3:57 am
This is not enough to go on sadly, try the debug log for more info?
August 2, 2017 at 6:00 pm
Hi Andy,
Your blog is great. It helps a lot to understand Metadata API’s. I have implemented this.
I am having one question for you that how you generate that XML file because when I retrieve meta data it returns a value which is having some URL but not those nods.
If possible please guide me with next step.
Thanks in advance.
August 9, 2017 at 2:17 am
Sure. Have you seen the examples in the GitHub repository? There is a deploy and retrieve example, as well as lots of examples in the metadataserviceexamples,cls file. If you don’t get xml back in the response it could be a salesforce issue. Try running your code in Execute Annonymous and looking for the response in the debug log. Hope this helps! 👍🏻
January 4, 2018 at 10:57 am
Hi Andrew,
Thank you for the API, saves people’s time. I’m trying to retrieve picklist based on record types using MetadataService (MetadataService.RecordType.picklistValues.picklist.values). The code is given below:
MetadataService.MetadataPort service = createService();
MetadataService.RecordType recordType = (MetadataService.RecordType) service.readMetadata(‘RecordType’, new String[] {.}).getRecords()[0];
for (MetadataService.RecordTypePicklistValue recordTypePicklist : recordtype.picklistValues) {
if ( == recordTypePicklist.picklist) {
// getting info on each value
for (MetadataService.PicklistValue picklistValue : recordTypePicklist.values) {
// printing the values
system.debug (picklistValue.fullName + ‘ – isActive? = ‘ + picklistValue.isActive);
}
}
}
Here, the picklistValue.fullName field is populated but isActive fields shows only null even though it skips the deactivated ones. Can I assume that by default, mdapi only pulls active pickliat values for the given recordtype?
Thank you in advance.
January 4, 2018 at 11:00 am
some code got auto-corrected. It is MetadataService.RecordType recordType = (MetadataService.RecordType) service.readMetadata(‘RecordType’, new String[] {objectName.recordTypeName}).getRecords()[0]; and
if (picklstFieldName == recordTypePicklist.picklist) {
January 4, 2018 at 6:17 pm
Yes, as this type is likely shared when defining picklist values, I would image you are correct it’s redundant in this context. I guess you could easily confirm this assertion by making some active and others inactive.
August 28, 2018 at 4:00 am
Andrew Sir, I m creating an Application using Metadata API. Your work helped me a lot. But few of the problems I m not able to find are :
1. Time and DateTime custom Field is not getting Created.
December 15, 2018 at 1:59 pm
Hi Aman, i’m really not sure what you are asking here? Can you rephrase and further qualify your question?
May 1, 2020 at 10:44 pm
This library is very helpful!
May 3, 2020 at 9:00 am
public String getPackageXml()
{
return ” +
” +
” +
‘HelloWorld’ +
‘ApexClass’ +
” +
‘26.0’ +
”;
}
public String getHelloWorldMetadata()
{
return ” +
” +
‘28.0’ +
‘Active’ +
”;
}
public String getHelloWorld()
{
return ‘public class HelloWorld’ +
‘{‘ +
‘public static void helloWorld()’ +
‘{‘ +
‘System.debug(\’ Hello World\’);’ +
‘}’ +
‘}’;
}
I don’t see test class, isn’t necessary?
Thanks Adelchi
May 9, 2020 at 6:54 am
Yes that will he needed
May 3, 2020 at 10:46 am
Hi, firstly let me congratulate for the immense job you did with metadataAPI. Second please explain why I can find a test class for helloworld class in here:
It is not necessary?
Thank you.
Adelchi
May 9, 2020 at 6:56 am
Thank you. The sample does not contain one but it’s easy to write one to cover such a basic class as bellow world. If you are not sure about apex test classes there is some great trial head resources and apex developers guide. Good luck and thanks
May 9, 2020 at 11:59 pm
Thanks Andrew, I added test classes, it works.
May 11, 2020 at 6:50 am
Great !
May 11, 2020 at 7:30 am
It really is. I am using it with ‘destructiveChanges’ as well. Using the zip.js is totally new for me, I did not know about it.
January 12, 2021 at 12:22 pm
Great one Andy. I am planning to use this for one of our use case. I have seen the last commit is 2 years ago and wanted to confirm if this is still being maintained and can I use this with the latest Salesforce API version?
February 6, 2021 at 11:54 am
It’s not something I have had time to update sadly. If the version last updated suites your needs you can still go ahead or tweak as need arises. I do have thoughts on how to better auto generate this going forward – but that’s another product.
February 10, 2021 at 10:53 pm
Hi Andy,
I’m not able to readMetadata() from a managed CustomObjectTranslation, it doesn’t return anything (gives null). I have no problem with standard objects or unmanaged custom objects.
Also, I’m using the same fullname that appears when using the listMetadata() method. I’ve tried with and without namespace prefix and still got nothing. Is this a known limitation? Or maybe I’m doing something wrong?
Example
This works fine in unmanaged package
MetadataService.CustomObjectTranslation customObjectTranslation = (MetadataService.CustomObjectTranslation)
service.readMetadata(‘CustomObjectTranslation’, new String[] {‘myCustomobject__c-en_GB’}).getRecords()[0];
This is not working in managed or in my scratch org
MetadataService.CustomObjectTranslation customObjectTranslation = (MetadataService.CustomObjectTranslation)
service.readMetadata(‘CustomObjectTranslation’, new String[] {‘prefiex__myCustomobject__c__c-en_GB’}).getRecords()[0];
Can anyone help me with this?
Thanks,
Kumbresh
March 27, 2021 at 10:23 am
This maybe a bug in the metadata api itself – the wrapper is pretty thin without much if any logic – one tip you can try is run your code in developer console anonymous apex and you will find in the debug log the raw xml sent to the server for your request and also response. Sometimes it helps to see things this way to discover things. Additionally you can reproduce your problem generally outside of this wrapper you will have a much better conversation with Salesforce support or other community references that just understand the raw metadata api – then once you can find what the problem is – revisit via the wrapper to try again. Hope this helps – you are so far doing the correct things to get the fully qualified name via list operation. Note finally that there is also some good metadata api support in the sfdx cli and developer workbench tools that you can also use to explore and contrast. Hope all this helps – good luck!