It seems like an absolute age since i first blogged about the new features Salesforce have added to the Spring’14 version of the Metadata API. This weekend my development org was finally upgraded and I was able to continue the work to upgrade the Apex wrapper around this API. The work is now complete and is formally the hardest and most time consuming upgrade yet, ultimately though the most worthwhile in terms of the way the API can now be used in Apex, it really feels like it has now finally unlocked the keys to the kingdom for Apex developers!
The last blog presented an example of reading and updating a Layout. With this release I’ve now extended this to all Metadata types. The introduction of the real time variants of the CRUD operations is a significant step forward in the usability of this library, particularly if your want to update rather than just create things.
Prior to Spring’14 you had to jump through quite a few hoops to read metadata, involving zip file handling in JavaScript and AJAX or Batch Apex code for handling asynchronous responses, as described in this blog. The async API variants still exist and are still supported, so all your code that used them before still works. There is also some reasons to consider still to use them, which i’ll cover later. Lets get into some new examples, all of which are available in MetadataServiceExamples.cls
Creating a Button and adding it to a Layout
// Create Custom Button MetadataService.WebLink webLink = new MetadataService.WebLink(); webLink.fullName = 'Test__c.googleButton'; webLink.availability = 'online'; webLink.displayType = 'button'; webLink.encodingKey = 'UTF-8'; webLink.hasMenubar = false; webLink.hasScrollbars = true; webLink.hasToolbar = false; webLink.height = 600; webLink.isResizable = true; webLink.linkType = 'url'; webLink.masterLabel = 'google'; webLink.openType = 'newWindow'; webLink.position = 'none'; webLink.protected_x = false; webLink.showsLocation = false; webLink.showsStatus = false; webLink.url = 'http://www.google.com'; webLink.width = 600; handleSaveResults( service.createMetadata( new List<MetadataService.Metadata> { webLink })[0]); // Read the Layout MetadataService.Layout layout = (MetadataService.Layout) service.readMetadata('Layout', new String[] { 'Test__c-Test Layout' }).getRecords()[0]; // Add the Custom Button to the Layout if(layout.customButtons==null) layout.customButtons = new List<String>(); layout.customButtons.add('googleButton'); // Update the Layout handleSaveResults( service.updateMetadata( new MetadataService.Metadata[] { layout })[0]);
Add a new Picklist Value
// Read Custom Field MetadataService.CustomField customField = (MetadataService.CustomField) service.readMetadata('CustomField', new String[] { 'Lead.picklist__c' }).getRecords()[0]; // Add Picklist 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]);
Copy Layout Sections between Layouts
// Read the source Layout MetadataService.Layout sourceLayout = (MetadataService.Layout) service.readMetadata('Layout', new String[] { 'Test__c-Test Template Layout' }).getRecords()[0]; // Read the target Layout MetadataService.Layout targetLayout = (MetadataService.Layout) service.readMetadata('Layout', new String[] { 'Test__c-Test Layout' }).getRecords()[0]; // Add section from source Layout to target Layout targetLayout.layoutSections.add( sourceLayout.layoutSections[0]); // Update target Layout handleSaveResults( service.updateMetadata( new MetadataService.Metadata[] { targetLayout })[0]);
More possibilities…
Salesforce are doing an amazing job with Metadata API coverage these days. If you can see it in the Setup menu, i’d say there is a good chance you can find it in the Metadata API list of support Metadata types. Here are a few interesting use cases that you might want to think about…
- Copy or merge tools. Ability to save admins potentially a significant number of clicks by writing tools to copy or merge Setup configuration.
- Setup Wizards. Scripting the creation more complex Setup config, such as Force.com sites or templating the growing set of org Settings.
- Field usage reporting. Read layouts, validation rules, workflows and search for field references.
- Writing your own Change Sets, You could write your own scripts to move specific metadata and/or sync things like picklists between orgs.
- Post install upgrades. Configuration UI to perform post install upgrades to none upgradable components, such as picklists and layouts in the subscriber org.
NOTE: One thing still not possible via the above CRUD methods is the creation of Apex Class or Apex Triggers, the only way (including the Tooling API) of doing this in a Production org, is still via the Metadata API deploy operation, thus the sample code for this in the library is still valid.
Upgrading to the new MetadataService.cls
For the first time since I’ve been refreshing the Apex wrapper MetadataService.cls, some of the metadata type classes and method signatures have changed. This is due to Salesforce changing the Metadata API of course, which they can do because they version at the end point, not at the WSDL level. I’m open to feedback on ways to avoid this in the future, such as the version on the end of the class, e.g. MetadataService30. The changes in this case which i have observed will effect require small changes to your use of the deploy method…
- MetadataService.checkDeployStatus now takes a boolean argument, called includeDetails, read more here,
- MetadataService.DeployResult class has a new details property replacing messages, read more here.
NOTE: The deploy example has been updated in the library with the above changes.
Scripting the Release Upgrade Process
I first described in my previous blog, that i have invested in developing a patcher script, a peace of Apex code i have written that parses the default Apex code output from the Generate Apex from WSDL button and automatically applies the usual changes i have been making manually to date to get things to work, plus some new changes to support the new readMetadata operation. I have included the patcher code in the library if you want to take a look at it and have updated the steps in the readme to create your own MetadataService.cls should you want to (in the event i don’t update it in the future for example). You do not need to worry about this if you plan to take the pre-patched MetadataService.cls from the repository, the above is really just an FYI.
Unsupported Metadata Types
The following Metadata types are not supported, as they utilise some specific features the above Patcher script does not support in relation to the use of shared Metadata type base classes. I’m confident these can be supported in the future and will like be working on these as part of the next release. I’ll be giving priority to those that have been the subject of recent questions , such as Flow and Sharing Rules.
- Flow
- Sharing Rules
- Role
- Custom Shortcut
- Email Folder
- Document Folder
Summary
Since its creation, i have been getting a steady flow of questions and requests for this library (evidence by the large examples class these days), so i can see there is demand for it in general, personally i’d still like to see it as a native library without the need to make the callouts. However until that time comes I’ll continue to support the community. Please do let me know what you’ve been getting up to with it and join in creating great community tools to help admins and developers be more productive!
April 25, 2014 at 8:53 am
This is so good, thanks for sharing. Opens up so many possibilities for tools – I’m having a play with it this weekend…
April 25, 2014 at 11:36 am
Excellent! Let me know how you get on! 🙂
April 25, 2014 at 1:38 pm
Hey Andrew
I’m currently in need to create and read Sharing Rules through API. I have seen that you put them here as unsupported, can you spare some details why? I’m curious what I will have to do to overcome this.
Thanks
April 25, 2014 at 3:58 pm
It’s due to them using an inheritance model in the WSDL, that needs to be manually applied in the generated code or ideally through the patcher. The fields in the base types need to be lifted to the top level types since the XML marshaller doesn’t look in base types when serializing and deserializing. Should be able to get it to work, actually looking at this problem for Workflow Actions now actually…. will keep you posted…
April 27, 2014 at 8:50 am
Ok so Workflow actions are done, but are way easier, since the base type didn’t actually have anything in them. I’ve reviewed the Sharing Rules metadata base types (see the WSDL) and they do. So will take either some manual effort or prefferably update the patch to modify the code (quicker for upgrades in the future). I’m thinking it maintains the inheritance of the types as per the XML Schema but the base types become empty with the top level types having all the fields, this is the only way to get it to work i think. Unfortunatly i don’t have a great deal of time this weekend, but may be able to get to this next week sometime. If you want to have a go at this yourself, i’d be happy to help co-ordainte, if your up for that raise a GitHub issue and we can talk more easily there. Thanks! 🙂
June 6, 2014 at 11:37 am
Just to let you know. I was trying to make it working but was not able, so I decided to contact support. After 6 weeks of R&D escalation they confirmed that it’s currently impossible to create sharing rule through createMetadata(). Here is the link to known issue: https://success.salesforce.com/issues_view?id=a1p30000000T4Q3AAK
June 6, 2014 at 6:51 pm
thanks for letting me know about this bug, once it’s fixed I will put more effort into adapting the apex Metadada API wrapper
December 17, 2014 at 2:51 pm
Hey Andrew
Is the sharing rule still unsupported?
Thanks.
December 18, 2014 at 8:26 am
Currently no, sorry. Needs an enhancement to the patcher class i use to modify the generated Apex. Please raise this as an enhancement on the GitHub issues site though.
December 22, 2014 at 2:24 pm
I’ve raised a GitHub issue here, https://github.com/financialforcedev/apex-mdapi/issues/65
December 29, 2014 at 4:35 am
Sorry could not get back to you earlier. Thanks for raising up as a github issue.
April 27, 2014 at 12:09 am
Hi, MetadataService.readMetadata() does not work for CustomObject itself. Below code throws exception saying “Missing dependent object: Class: MetadataService.readCustomObjectResponse_element”.
service.readMetadata(‘CustomObject’, new String[] {‘Job_Application__c’}).getRecords();
Looking the debug print, the SOAP response is returned successfully, so it seems an issue is in client-side conversion. Any idea how to fix this issue?
April 27, 2014 at 8:37 am
This seems to work for me, I’ve just tried this, https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls#L757. Are you sure your using the latest MetadataService.cls? It has to contain the class readCustomObjectResponse_element (amongst others).
April 27, 2014 at 2:33 pm
The latest version works greatly! Thank you very much.
May 7, 2014 at 11:03 pm
Hi Andrew, thanks so much for getting this update done, it’s awesome. I thought I might leave you with a Gist I put together that queries the Tooling API for all Workflow names, constructs the Tablename.Workflowname syntax and passes to the readMetadata service. This is written in a batchable class due to the 10 callout limit per request. I’m in the process of working through saving the metadata information and then searching. Ultimately we are finding it extremely difficult to complete role and profile reviews because if we change a profile or role name, we impact things like validation rules and workflow rules. Anyways, feel free to critique and add suggestions if you like!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
WorkflowRuleBatch.java
hosted with ❤ by GitHub
May 8, 2014 at 7:42 am
Nice thanks for sharing! Will take a look! Did you use the Apex Tooling API by any chance? 🙂
May 8, 2014 at 4:05 pm
global List start(Database.BatchableContext bc)
{
String salesforceHost = System.Url.getSalesforceBaseURL().toExternalForm();
String urlHost = salesforceHost + ‘/services/data/v30.0/tooling/query/?q=Select+Id+,Name,+TableEnumOrId+from+WorkflowRule’;
HTTPResponse res = metadataQuery(urlHost);
//system.debug(‘workflow res = ‘ + res);
//system.debug(‘workflow res.getBody() = ‘ + res.getBody());
List workflowRuleNames = new List();
WorkflowRule wfrs = (WorkflowRule) System.JSON.deserialize(res.getBody(), WorkflowRule.class);
for(Integer j=0; j < wfrs.records.size(); j++ )
{
String fullName = wfrs.records[j].TableEnumOrId + '.' + wfrs.records[j].Name;
workflowRuleNames.add(fullName);
}
return workflowRuleNames;
}
Absolutely 😀
May 8, 2014 at 4:07 pm
Nice, check out the Apex Tooling API wrapper to avoid some of the Json plumbing you have going on here though.
May 8, 2014 at 4:13 pm
I would but my understand was that the Tooling API Wrapper doesn’t currently support WorkflowRule and I was just looking to do a quick POC. Let me know if I’m wrong on this one.
May 8, 2014 at 4:16 pm
It looks like they just added it! Which means we need to update the Apex Tooling API wrapper! 🙂 http://www.salesforce.com/us/developer/docs/api_tooling/Content/sforce_api_objects_workflowrule.htm Want to help?
May 8, 2014 at 4:17 pm
Sure I’ll take a look at it 🙂
May 8, 2014 at 4:21 pm
Nice! An easy way to generate the Apex Types is to run the WSDL to Apex generator then copy and paste the types (stripping out the XML marshalling metadata). Saves having to reverse engineer the fields and type structures from the JSON returned if you use the WSDL as a reference. Note that the generate types from the WSDL don’t include the inheritance for example extending WorkflowAction (as schema types do), but you can apply this manually also.
May 8, 2014 at 6:14 pm
Update for Workflow Rule to the ToolingAPI has been submitted.
May 8, 2014 at 6:15 pm
Wow that was fast! Will take a look! Thanks for the contribution!
May 9, 2014 at 2:11 am
I’ve been searching for a way to get access to the properties of a report folder. Has anyone done this via the metadata API. I can get a list of the report folders and then get information about the individual reports but what I want is the information on the actual report folder itself so that I can see who the report is sharedTo. Any help would be greatly appreciated!
May 9, 2014 at 9:16 am
This looks like it is available via the Metadata API yes, i need to do some work on the Apex wrapper if you want to call it from Apex, if so can you raise an item here, i’ll start work on it today but may not get completed until early next week.https://github.com/financialforcedev/apex-mdapi/issues?state=open
May 9, 2014 at 11:29 am
Do you know what the parameters for the call would like? Would this be through the listmetada()?
Thanks!
May 9, 2014 at 11:32 am
I am using the readMetadata method, though other changes are required to get folders working, I can continue with these early next week. Or if you familar with XML serialisation in apex internals I can commit what I have now ?
May 9, 2014 at 12:18 pm
Can you comment on what the parameters look like with readmetadata()? I can then mimic it through soap. Thanks
May 9, 2014 at 2:42 pm
Take a look at this, https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls#L796, if you run this in the Debug log will be the SOAP and the SOAP response, before it errors because the Apex Types still need a bit of work (flattening of the members into the top level types).
June 18, 2014 at 11:32 am
Andrew, I am able to connect salesforce org, retrieve SOQL query result using C# coding (with visual studio). I am trying to figure out the way to add picklist value through C# coding. Is it possible using MetaData API?
June 18, 2014 at 2:04 pm
Salesforce recently annoucned this, http://www.wadewegner.com/2014/01/announcing-the-salesforce-toolkits-for-net/, not sure if this support Metadata API. Otherwise you will have to download the WSDL and import it into VisualStudio like any other Web Service. You should be able to use the toolkit to easily get the authentication token and set that into the approprite header for the Metadata API generated classes from the WSDL.I also did a google for ‘.net salesforce metadata api’, found this, http://www.pocketsoap.com/weblog/2008/01/1803.html and this https://developer.salesforce.com/page/Integrating_Force.com_with_Microsoft_.NET
June 19, 2014 at 11:24 am
Thanks Andrew
I am able to retrieve field’s picklist values using my C# code. As per requirement, I am trying to add some values to the existing picklist using MetaData API.
June 19, 2014 at 1:01 pm
This is also possible be you need to retrieve first. check the apex examples class file for this. Should be roughly same in c#
August 14, 2014 at 11:46 am
Hi Andrew Fawcett, Excellent work. I have one question. Can we update package version through metadata it?
August 14, 2014 at 1:05 pm
Yes indeed you can.
November 17, 2014 at 3:29 pm
I need to do 2 things with apex
1. Make a copy of a layout
2. Grab a layout from an installed package and update another layout
Are these possible? Great blog!
December 22, 2014 at 2:27 pm
1. Yes, this is possible, retrieve the layout, change the fullName and use createMetadata or upsertMetadata
2. You can only get the initial installations of managed layouts, after that, package upgrades don’t update layouts. But yes, you can retrieve these and use the information to update another layout.
P.S. Sorry i missed this comment until now! 😦
January 17, 2015 at 3:43 pm
Hey, I was trying to share a report using the Metadata,FolderShare class but I am geting an error.
Can you help me with this?
I saw a thread above with respect to this. Were you able to do this?
I am trying it like this:
MetadataService.ReportFolder rfa = (MetadataService.ReportFolder) service.readMetadata(‘ReportFolder’, new String[]{‘test’}).getRecords()[0];
MetadataService.FolderShare folderShareService = new MetadataService.FolderShare();
folderShareService.accessLevel = ‘View’;
folderShareService.sharedTo = ‘AllInternalUsers’;
folderShareService.sharedToType = ‘Organization’;
rfa.folderShares.add(folderShareService);
service.updateMetadata( new MetadataService.Metadata[] { rfa });
January 18, 2015 at 10:50 am
Yeah probably can sort this, can you raise this as a GitHub issue please? I’ll take a look.
January 28, 2015 at 7:54 pm
Hi Andy,
Is it possible to create sharing rules using Metadata? If yes, can you add a sample to create or read a sharing rule in your example class https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls
I have been trying to do so, but always getting an error.
If not in version 32.0, will it be supportable in version 33.0 as in version 33.0 salesforce is will change the components containing sharing rules.
January 30, 2015 at 9:55 am
It is currently not possible, i hope once the library is updated to 33 (Spring’15). I will wait for the service to be upgraded early Feb before doing this however.
March 10, 2016 at 4:19 pm
Nice work Andy. I have an issue when using the saveResult method. Here is the code
List results = service.updateMetadata(
new MetadataService.Metadata[] { customField });
handleSaveResults(results[0]);
and here is the error I’m getting “Method does not exist or incorrect signature: handleSaveResults(MetadataService.SaveResult)”
Thank you for your help.
March 10, 2016 at 6:56 pm
Does the method exist?
March 10, 2016 at 7:00 pm
I used the https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls and https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataService.cls classes.
March 10, 2016 at 7:47 pm
Here is the full code:
public class MyTestingUpdatePicklist {
public static void updatePicklistField()
{
MetadataService.MetadataPort service = MetadataServiceExamples.createService();
MetadataService.CustomField customField = new MetadataService.CustomField();
customField.fullName = ‘Opportunity.incumbent__c’;
customField.label = ‘Incumbent’;
customField.type_x = ‘Picklist’;
metadataservice.Picklist pt = new metadataservice.Picklist();
pt.sorted= false;
metadataservice.PicklistValue eyecare = new metadataservice.PicklistValue();
eyecare.fullName= ‘eyecare’;
eyecare.default_x=false ;
metadataservice.PicklistValue eyeone = new metadataservice.PicklistValue();
eyeone.fullName= ‘eyeone’;
eyeone.default_x=false ;
pt.picklistValues = new list{eyecare,eyeone};
customField.picklist = pt ;
List results = service.updateMetadata(
new MetadataService.Metadata[] { customField });
handleSaveResults(results[0]);
// handleSaveResults( service.updateMetadata(
// new MetadataService.Metadata[] { customField })[0]);
// SaveResult[] results = metadataConnection.updateMetadata(Metadata[] customField);
}
}
Thank you so much Andy fro your help.
January 10, 2017 at 6:12 am
Hi Andrew,
Thanks for your kindly sharing.
I have followed the steps to read a custom picklist field metadata,in which “Position__c” is a custom object and “Sub_Status__c” is a custom picklist field of position object:
system.debug((MetadataService1.CustomField)stub.readMetadata(‘CustomField’,new String[]{‘Position__c.Sub_Status__c’}).getRecords()[0]);
But got the exceptions as below:
System.CalloutException: Web service callout failed: Unable to parse callout response. Apex type not found for element valueSet.
It seems that there is a element called valueSet in callout response which Apex can not parse properly, and I don’t know how to solve this problem.
January 14, 2017 at 12:12 am
Yes that does seem odd, can you post sample code and issue on the github repo please
Pingback: Add ‘Detail Page Button’ to Contact Layout using the MetaData API – GrindSkills