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 });

2 Answers 2


It turns out that https://[instance].salesforce.com/services/data/v27.0/tooling/sobjects/ApexClass 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].salesforce.com/services/data/v27.0/sobjects/ApexTrigger. 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].salesforce.com/services/data/v27.0/sobjects:

$ curl -H 'X-PrettyPrint: 1' -H 'Authorization: Bearer 00Di0000000I4NV!ARgAQDLUBCU38O9RXONyu5C99tcHYYQCc7dOHW8i6ZiGEgPBxyKZwumKsdE69ElKM0ipCD4oCDYbOsmZKNJ3FUqUhsyFk3zu' https://na15.salesforce.com/services/data/v27.0/sobjects | grep \"name\"\ :\ \"Apex
    "name" : "ApexClass",
    "name" : "ApexComponent",
    "name" : "ApexLog",
    "name" : "ApexPage",
    "name" : "ApexTestQueueItem",
    "name" : "ApexTestResult",
    "name" : "ApexTrigger",
  • 1
    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. Commented May 6, 2013 at 22:02
  • 1
    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. Commented May 6, 2013 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 = ToolingApi.post("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;
  • 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. Commented Mar 19, 2013 at 22:53
  • Ah - I was expecting that to be a required field, since it's required when creating a new Trigger from the Force.com 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
    Commented Mar 19, 2013 at 23:17
  • Had another random idea --- try 'opportunity', lowercase.
    – zachelrath
    Commented Mar 20, 2013 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 Force.com Migration Tool, the Salesforce user interface, or the Force.com 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? salesforce.com/us/developer/docs/api/Content/… Commented Mar 20, 2013 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. Commented Mar 20, 2013 at 20:28

You must log in to answer this question.

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