Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

How do I use the Tooling API to create a new Apex Trigger (rather than update an existing one)?

According to the documentation I need to specify the ContentEntityId as an existing Trigger Id. But in my case I don't have an Id because I've trying to create a new sObject.

If I omit the ContentEntityId I get an error back in the SaveResult:

Message: "insufficient access rights on cross-reference id"

The following is more or less a test case in C# that attempts to use the SOAP tooling API to create the new trigger.

SforceService toolingService = //... Get instance of the Tooling API

MetadataContainer container = new MetadataContainer();
container.Name = "Test" + DateTime.Now.Ticks;

SaveResult[] saveResult = toolingService.create(new sObject[] { container });
string metaDataContainerId = saveResult[0].id;

ApexTriggerMember apexTriggerMember = new ApexTriggerMember();
apexTriggerMember.MetadataContainerId = metaDataContainerId;
apexTriggerMember.Body = "trigger TestOpportunityBeforeUpdate on Opportunity (before update) {\n//Horray\n}";
    // Adding this will successfuly update the existing trigger
//apexTriggerMember.ContentEntityId = "01q70000000GuzP";

SaveResult[] triggerSaveResults = toolingService.create(new sObject[] { apexTriggerMember });
Assert.AreEqual(1, triggerSaveResults.Length);
SaveResult triggerSaveResult = triggerSaveResults[0];
if (!triggerSaveResult.success)

ContainerAsyncRequest containerRequest = new ContainerAsyncRequest();
containerRequest.MetadataContainerId = metaDataContainerId;
containerRequest.IsCheckOnly = false;

SaveResult[] containerResults = toolingService.create(new sObject[] { containerRequest });
share|improve this question
up vote 11 down vote accepted

It turns out that https://[instance] is a special case, and, in the general case (ApexComponent, ApexPage, ApexTrigger) you use the regular REST API sobject URL with the appropriate sObject type.

For ApexTrigger, this would be https://[instance] So to create a trigger:

$ curl -H 'X-PrettyPrint: 1' -H 'Authorization: Bearer 00D...' \
    -H 'Content-Type: application/json' \
    -d '{ "Name" : "TestTrigger", \
          "TableEnumOrId" : "Opportunity",\
          "Body" : "trigger TestTrigger on Opportunity (after insert){}" }' \
  "id" : "01qi0000000DKwyAAG",
  "success" : true,
  "errors" : [ ]

You can see all the code artifact sObject types listed if you GET https://[instance]

$ curl -H 'X-PrettyPrint: 1' -H 'Authorization: Bearer 00Di0000000I4NV!ARgAQDLUBCU38O9RXONyu5C99tcHYYQCc7dOHW8i6ZiGEgPBxyKZwumKsdE69ElKM0ipCD4oCDYbOsmZKNJ3FUqUhsyFk3zu' | grep \"name\"\ :\ \"Apex
    "name" : "ApexClass",
    "name" : "ApexComponent",
    "name" : "ApexLog",
    "name" : "ApexPage",
    "name" : "ApexTestQueueItem",
    "name" : "ApexTestResult",
    "name" : "ApexTrigger",
share|improve this answer
Fair enough. The answer is that you don't use the ToolingAPI at all as the regular REST API (or Partner API) can do it. – Daniel Ballinger May 6 '13 at 22:02
The naming of the ToolingAPI is a little confusing. It's really just a new set of components (sObjects for the most part) that are layered on top of the standard REST or SOAP APIs. – Ralph Callaway May 6 '13 at 22:07

I believe that to create new entities, whether ApexClass, ApexPage, or ApexTrigger, you will need to directly create instances of these entities, rather than using ApexTriggerMember, ApexClassMember, or ApexPageMember. The _Member variants, wrapped in a MetadataContainer, only appears to be necessary for update operations.

From Pat Patterson's article on the Tooling API, here's an example using the REST variant of the Tooling API, from within Java:

Create an Apex Class

To create an Apex Class, post JSON containing the class name and body to sobjects/ApexClass.

String name = "Demo"; 
String body = "public class " + name + "{\n\n}";

JSONObject apexClassRequest = new JSONObject();
apexClassRequest.put("Name", name);
apexClassRequest.put("Body", body);
JSONObject apexClassResponse ="sobjects/ApexClass", apexClassRequest);
System.out.println("ApexClass id: " + apexClassResponse.get("id"));

Notice that the write is to ApexClass, not to ApexClassMember.

So, I'm not a C# guy, but it would appear to be as simple as doing a similar call to the ApexTrigger object, with some added fields:

SforceService toolingService = //... Get instance of the Tooling API

ApexTrigger newTrigger = new ApexTrigger();
newTrigger.Name = 'TestOpportunityBeforeUpdate';
newTrigger.TableEnumOrId = 'opportunity';
newTrigger.Body = 'trigger TestOpportunityBeforeUpdate on Opportunity (before update{ \n//Horray\n }';

SaveResult[] results = toolingService.create(new sObject[] { newTrigger });
String triggerId = results[0].id;
share|improve this answer
You are definitely on the right track. I did a quick test and got the following error in the SaveResult: Missing field [TableEnumOrId] on trigger create. Setting it to "Opportunity" then resulted in the error INVALID_FIELD_FOR_INSERT_UPDATE "No access to entity 'Opportunity'". I'll keep hacking at it. – Daniel Ballinger Mar 19 '13 at 22:53
Ah - I was expecting that to be a required field, since it's required when creating a new Trigger from the IDE, but didn't see any corresponding field in the Metadata API docs for ApexTrigger. But No Access to entity 'Opportunity' is definitely weird. Can't think of anything else that TableEnumOrId would be other than 006. – zachelrath Mar 19 '13 at 23:17
Had another random idea --- try 'opportunity', lowercase. – zachelrath Mar 20 '13 at 11:17
The docs for this object state 'Although Apex classes and triggers have the Create and Update field properties, a runtime exception occurs if you try to create or update them using the API. Instead, use the Migration Tool, the Salesforce user interface, or the IDE to create or update Apex classes or triggers'. Oddly it also states this for the ApexClass object as well, yet clearly it seems possible to do it using the regular REST CRUD API's, supported or just good luck for ApexClass?… – Andrew Fawcett Mar 20 '13 at 12:24
I tired with the lower case "opportunity", same result. I also tried creating an ApexClass, that worked just fine. It might just be that the support isn't there yet for the Tooling API. – Daniel Ballinger Mar 20 '13 at 20:28

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.