81

While researching a semi-related question posted here earlier, I dug up some interesting and conflicting information regarding whether it's even possible to use the Metadata API from within Apex. I used to be of the belief that this was strictly forbidden, as others seem to agree with. In a related post a Force.com MVP states:

"You can't callout to any Salesforce web services from within Salesforce. You can only callout to external web services."

But looking around I found some hopeful posts along with a more recent post at the Force.com Cookbook which suggests that it should be possible. I've discovered plenty of people who claim that it is possible, though so far I can find absolutely nothing confirming that anyone has actually done this successfully.

For example, from this post:

Yes. You can use Ajax Toolkit to call Metadata API calls, but this possible only in VisualForce pages not Apex Class.
http://www.salesforce.com/us/developer/docs/ajax/index.htm

So if you (or anyone you know) has done this and can share the details of the process, or at least the steps to achieve a login and then a simple create() from within Apex, please share your results!

1
  • 2
    Also, I realize you can't currently parse .ZIP files in Apex, but the main objective here is to simply log in to an org and push one metadata record. So if this is "possible" but parsing the return value isn't, then that would still be great to know.
    – Adam
    Sep 2, 2012 at 1:35

2 Answers 2

55

it's definitely possible to use CRUD based Metadata API calls in APEX. salesforce to salesforce and even loopback inside the same instance, we tried both. Adam already mentioned that a major part of the functionality is based on zipped metadata packages which cannot be created or unzipped using apex. however you can still create custom fields or objects using the create call which can be pretty useful. The ugly part of this is WSDL2APEX as it does not support schema type extensions but the Metadata WSDL has a super type Metadata that all components inherit from. We tried to modifiy the metadata WSDL and code WSDL2APEX produced but we ended up sending our own SOAP messages. Here's some simple code I just tested that illustrates how APEX can be leveraged to create a new custom object, just paste it into the developer console to test it:

HTTP h = new HTTP();
HTTPRequest req = new HTTPRequest();
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml');
req.setHeader('SOAPAction', 'create');

String b = '<?xml version="1.0" encoding="UTF-8"?>';
b += '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
b += '<soapenv:Header>';
b += '<ns1:SessionHeader soapenv:mustUnderstand="0" xmlns:ns1="http://soap.sforce.com/2006/04/metadata">';
b += '<ns1:sessionId>' + UserInfo.getSessionId() + '</ns1:sessionId>';
b += '</ns1:SessionHeader>';
b += '</soapenv:Header>';
b += '<soapenv:Body>';
b += '<create xmlns="http://soap.sforce.com/2006/04/metadata">';
b += '<metadata xsi:type="ns2:CustomObject" xmlns:ns2="http://soap.sforce.com/2006/04/metadata">';
b += '<fullName>sample__c</fullName>';
b += '<deploymentStatus>Deployed</deploymentStatus>';
b += '<description>created by the Metadata API</description>';
b += '<enableActivities>true</enableActivities>';
b += '<label>sample Object</label>';
b += '<nameField>';
b += '<displayFormat>AN-{0000}</displayFormat>';
b += '<label>sample__c Name</label>';
b += '<type>AutoNumber</type>';
b += '</nameField>';
b += '<pluralLabel>sample Objects</pluralLabel>';
b += '<sharingModel>ReadWrite</sharingModel>';
b += '</metadata>';
b += '</create>';
b += '</soapenv:Body>';
b += '</soapenv:Envelope>';

req.setBody(b);
req.setCompressed(false);
req.setEndpoint('https://na12-api.salesforce.com/services/Soap/m/25.0');
HTTPResponse resp = h.send(req);
System.debug(resp.getBody());

you probably have to adjust the endpoint to point to your server. Using some Visualforce and a actionPoller component you can develop a nice object builder and even check if the asynchronous object creation was succussful.

8
  • 1
    I'm really looking forward to testing this out later. If I can create a trigger in this fashion, this will be the best news I've had in a long time. Thank you h9nry!! If I can reproduce I'll surely mark this as answered.
    – Adam
    Sep 3, 2012 at 21:03
  • 1
    That worked - thanks again! Trigger creation is the ultimate goal, which I'll try to accomplish later.
    – Adam
    Sep 3, 2012 at 21:13
  • 1
    Unfortunately, although I'm able to create a custom object in this fashion, I'm thinking that creating a trigger is impossible since deploy() requires a ZIP file to be passed in, unlike create(), unless I'm missing something. Thoughts?
    – Adam
    Sep 4, 2012 at 2:48
  • 1
    i'd assume that you would have to follow the standard development process for triggers and classes. so in order to create one in a production environment it has to be deployed, and deployment as you already mentioned requires zip file handling. however a fascinating idea...
    – h9nry
    Sep 4, 2012 at 6:50
  • 1
    Though it feels like a dead-end hack, I'm wondering if leveraging something like this might work: stuartk.com/jszip. Binding a VF/Apex binary member var to receive the ZIP might be challenging though.
    – Adam
    Sep 4, 2012 at 17:41
50

I can confirm that it is indeed possible as h9nry has pointed out. I also had some issues getting the WSDL2Apex tool to generate anything usable, but was finally successful!

I really wanted to use the generated Apex code rather than construct and parse the XML. The main key is getting it to output the xsi:type attribute to indicate the type of component your dealing with. Once you sort this you can do things like this!

MetadataService.MetadataPort service = createService();     
MetadataService.CustomObject customObject = new MetadataService.CustomObject();
customObject.fullName = 'Test__c';
customObject.label = 'Test';
customObject.pluralLabel = 'Tests';
customObject.nameField = new MetadataService.CustomField();
customObject.nameField.type_x = 'Text';
customObject.nameField.label = 'Test Record';
customObject.deploymentStatus = 'Deployed';
customObject.sharingModel = 'ReadWrite';
MetadataService.AsyncResult[] results = 
    service.create(new List<MetadataService.Metadata> { customObject });

I've uploaded the modified Apex Metadata class to Github at https://github.com/financialforcedev/apex-mdapi. Along with a more detailed description of whats possible and what is not. As has previously been pointed out most of the methods return an async result, meaning you have to implement some kind of polling solution. I would love to work with someone to contribute a VF component perhaps for this?

I've also been wondering about the Metadata REST API, as its sitll in pilot. Having spoken to Salesforce it does seem that the GA release of this is sadly some way out. So if your keen to do this, and there are some really useful use cases to want to do so! Hopefully this will help in the meantime!

https://github.com/financialforcedev/apex-mdapi

13
  • 1
    Thanks you, Andrew, for this excellent post and all the work you did over at github. The day that we can use all of the Metadata API from Apex, including creating triggers, will be a very good day.
    – Adam
    Oct 11, 2012 at 19:08
  • 1
    Your very welcome Adam and thank you! It was my pleasure. Watch out for a few more tweaks to it, I've got an internal project that needs it and I'll likely upload a few more useful bits and peaces to it in the coming weeks. Oct 11, 2012 at 22:43
  • 3
    Adam you may be interested to know that I have got a Metadata Retrieve working using the JSZip library! Take a look at the code and screenshot here! I'll likely upload the a Deploy for Apex soon! github.com/financialforcedev/apex-mdapi#metadata-retrieve-demo Oct 14, 2012 at 16:32
  • 2
    You're a pioneer, Andrew. I got as far as looking into JavaScript-based zip utils and felt like I had drifted into the weeds. I returned to assuming that, for now, an external service such as Heroku was the only viable architectural choice to dynamically deploy triggers into customer orgs. The problem is that I don't want to have to manage a Heroku instance because the intention is to allow a managed package on the AppExchange deploy triggers into the orgs it's installed in. Therefore, for scalability as the product sells, it really would be better to keep it in Apex. Thanks for the update!
    – Adam
    Oct 14, 2012 at 20:54
  • Updated with a deploy example now, enjoy! github.com/financialforcedev/apex-mdapi#metadata-deploy-demo Oct 22, 2012 at 13:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .