Skip to main content
The Tooling API provides access to Salesforce development tools and metadata objects. It’s designed for building custom development tools and IDE integrations.

Overview

The Tooling API supports:
  • CRUD operations on tooling objects (ApexClass, ApexTrigger, etc.)
  • Executing anonymous Apex
  • Running Apex tests synchronously and asynchronously
  • Querying tooling objects with SOQL
  • Code completion for Apex and Visualforce
  • Same query/DML methods as the standard Connection

Accessing the Tooling API

const conn = new jsforce.Connection({ /* ... */ });
const tooling = conn.tooling;

Standard Operations

The Tooling API provides the same CRUD and query methods as the main Connection:

query()

Query tooling objects using SOQL.
// Query Apex classes
const result = await conn.tooling.query(
  "SELECT Id, Name, ApiVersion, Status FROM ApexClass WHERE Name LIKE 'Test%'"
);

console.log('Found', result.totalSize, 'test classes');
for (const cls of result.records) {
  console.log('Class:', cls.Name, 'v' + cls.ApiVersion);
}

// Query with typed results
interface ApexClass {
  Id: string;
  Name: string;
  Body: string;
  ApiVersion: number;
}

const classes = await conn.tooling.query<ApexClass>(
  'SELECT Id, Name, Body, ApiVersion FROM ApexClass LIMIT 10'
);

sobject()

Get a tooling SObject for CRUD operations.
const apexClass = conn.tooling.sobject('ApexClass');
const apexTrigger = conn.tooling.sobject('ApexTrigger');
const validationRule = conn.tooling.sobject('ValidationRule');

create()

Create tooling objects.
// Create an Apex class
const result = await conn.tooling.sobject('ApexClass').create({
  Name: 'MyNewClass',
  Body: 'public class MyNewClass { public void doSomething() {} }',
  ApiVersion: 58.0,
  Status: 'Active'
});

console.log('Created class:', result.id);

// Create multiple records
const results = await conn.tooling.create('ApexClass', [
  { Name: 'Class1', Body: 'public class Class1 {}' },
  { Name: 'Class2', Body: 'public class Class2 {}' }
]);
Alias: insert()

retrieve()

Retrieve records by ID.
const apexClass = await conn.tooling.retrieve('ApexClass', '01pxx000000ABCD');
console.log('Class body:', apexClass.Body);

// Retrieve multiple records
const classes = await conn.tooling.retrieve('ApexClass', [
  '01pxx000000ABCD',
  '01pxx000000EFGH'
]);

update()

Update tooling objects.
const result = await conn.tooling.sobject('ApexClass').update({
  Id: '01pxx000000ABCD',
  Body: 'public class MyNewClass { public void updated() {} }'
});

console.log('Updated:', result.success);

delete()

Delete tooling objects.
await conn.tooling.delete('ApexClass', '01pxx000000ABCD');

// Delete multiple
await conn.tooling.destroy([
  '01pxx000000ABCD',
  '01pxx000000EFGH'
]);
Aliases: destroy(), del()

upsert()

Upsert records using an external ID field.
const result = await conn.tooling.upsert('CustomObject', {
  DeveloperName: 'MyCustomObject',
  // ... other fields
}, 'DeveloperName');

Describe Operations

describe()

Describe a tooling object.
const metadata = await conn.tooling.describe('ApexClass');

console.log('Label:', metadata.label);
console.log('Fields:', metadata.fields.map(f => f.name));

// Use cached version
const cached = await conn.tooling.describe$('ApexClass');

// Force immediate cache update
const updated = await conn.tooling.describe$$('ApexClass');
Aliases: describeSObject(), describeSObject$(), describeSObject$$()

describeGlobal()

Get all available tooling objects.
const global = await conn.tooling.describeGlobal();

console.log('Tooling objects:');
for (const sobject of global.sobjects) {
  console.log('-', sobject.name, '(' + sobject.label + ')');
}

// Cached versions
await conn.tooling.describeGlobal$();
await conn.tooling.describeGlobal$$();

Apex Execution

executeAnonymous()

Execute anonymous Apex code.
body
string
required
Apex code to execute
result
ExecuteAnonymousResult
Execution result
result.compiled
boolean
Whether the code compiled successfully
result.success
boolean
Whether the execution succeeded
result.line
number
Line number where error occurred (if any)
result.column
number
Column number where error occurred (if any)
result.compileProblem
string | null
Compilation error message
result.exceptionMessage
string | null
Runtime exception message
result.exceptionStackTrace
string | null
Runtime exception stack trace
const code = `
  System.debug('Hello from anonymous Apex!');
  Account acc = new Account(Name = 'Test Account');
  insert acc;
  System.debug('Created account: ' + acc.Id);
`;

const result = await conn.tooling.executeAnonymous(code);

if (result.compiled && result.success) {
  console.log('Execution successful!');
} else if (!result.compiled) {
  console.error('Compilation error:', result.compileProblem);
  console.error('Line', result.line, 'Column', result.column);
} else {
  console.error('Runtime error:', result.exceptionMessage);
  console.error('Stack trace:', result.exceptionStackTrace);
}

Apex Testing

runTestsSynchronous()

Run Apex tests synchronously and wait for results.
request
RunTestsRequest
required
Test execution request
request.tests
TestsNode[]
required
Tests to run
tests[].classId
string
Apex class ID (use classId OR className)
tests[].className
string
Apex class name (use classId OR className)
tests[].testMethods
string[]
Specific test methods to run (omit to run all)
request.maxFailedTests
number
Stop after N failures
request.testLevel
RunTestLevel
Test level: RunSpecifiedTests, RunLocalTests, RunAllTestsInOrg
request.skipCodeCoverage
boolean
Skip code coverage calculation (default: false)
result
RunTestsResult
Test execution results
result.numTestsRun
number
Total tests executed
result.numFailures
number
Number of test failures
result.successes
RunTestSuccess[]
Successful test methods
result.failures
RunTestFailure[]
Failed test methods with error details
result.codeCoverage
CodeCoverageResult[]
Code coverage results
result.totalTime
number
Total execution time in milliseconds
// Run specific test class
const result = await conn.tooling.runTestsSynchronous({
  tests: [
    { className: 'MyTestClass' }
  ]
});

console.log('Tests run:', result.numTestsRun);
console.log('Failures:', result.numFailures);
console.log('Total time:', result.totalTime, 'ms');

// Display failures
for (const failure of result.failures) {
  console.log('FAILED:', failure.name + '.' + failure.methodName);
  console.log('  Message:', failure.message);
  console.log('  Stack:', failure.stackTrace);
}

// Display coverage
for (const coverage of result.codeCoverage) {
  const percent = ((coverage.numLocations - coverage.numLocationsNotCovered) / coverage.numLocations * 100).toFixed(1);
  console.log(`Coverage for ${coverage.name}: ${percent}%`);
}

// Run specific test methods
const methodResult = await conn.tooling.runTestsSynchronous({
  tests: [
    {
      className: 'MyTestClass',
      testMethods: ['testMethod1', 'testMethod2']
    }
  ]
});

// Run all local tests
const localTests = await conn.tooling.runTestsSynchronous({
  testLevel: 'RunLocalTests'
});

runTestsAsynchronous()

Run Apex tests asynchronously.
request
RunTestsAsyncRequest
required
Test execution request
request.classids
string
Comma-separated class IDs
request.classNames
string
Comma-separated class names
request.suiteids
string
Comma-separated test suite IDs
request.suiteNames
string
Comma-separated test suite names
request.tests
TestsNode[]
Alternative: array of test configurations
request.testLevel
RunTestLevel
Test level
jobId
string | null
Async test job ID (query ApexTestQueueItem/ApexTestResult for status)
// Start async test run
const jobId = await conn.tooling.runTestsAsynchronous({
  classNames: 'MyTestClass,AnotherTestClass'
});

console.log('Test job started:', jobId);

// Poll for completion
const pollResults = async (jobId) => {
  const queueItems = await conn.tooling.query(
    `SELECT Id, Status, ApexClassId FROM ApexTestQueueItem WHERE ParentJobId = '${jobId}'`
  );
  
  const allComplete = queueItems.records.every(item => 
    item.Status === 'Completed' || item.Status === 'Failed'
  );
  
  if (!allComplete) {
    console.log('Tests still running...');
    setTimeout(() => pollResults(jobId), 1000);
    return;
  }
  
  // Get results
  const results = await conn.tooling.query(
    `SELECT Outcome, MethodName, Message, StackTrace 
     FROM ApexTestResult 
     WHERE AsyncApexJobId = '${jobId}'`
  );
  
  for (const result of results.records) {
    console.log(result.MethodName, ':', result.Outcome);
    if (result.Outcome === 'Fail') {
      console.log('  Message:', result.Message);
    }
  }
};

await pollResults(jobId);

Code Completion

completions()

Get code completions for Apex or Visualforce.
type
'apex' | 'visualforce'
Completion type (default: β€˜apex’)
result
CompletionsResult
Completion information
result.publicDeclarations
object
For Apex: namespaced public class declarations with methods/properties
result.completions
object
For Visualforce: component completions with attributes
// Get Apex completions
const apexCompletions = await conn.tooling.completions('apex');

// System namespace classes
const systemClasses = apexCompletions.publicDeclarations?.System;
if (systemClasses) {
  const debugClass = systemClasses.System; // System.System is the System class
  console.log('System.debug parameters:', debugClass.methods.find(m => m.name === 'debug'));
}

// Get Visualforce completions
const vfCompletions = await conn.tooling.completions('visualforce');

for (const [component, info] of Object.entries(vfCompletions.completions || {})) {
  console.log('Component:', component);
  console.log('Attributes:', Object.keys(info.attribs));
}

Common Tooling Objects

Here are some commonly used tooling objects:
  • ApexClass: Apex class definitions
  • ApexTrigger: Apex trigger definitions
  • ApexPage: Visualforce page definitions
  • ApexComponent: Visualforce component definitions
  • ValidationRule: Validation rule definitions
  • WorkflowRule: Workflow rule definitions
  • CustomObject: Custom object metadata
  • CustomField: Custom field metadata
  • ApexLog: Debug logs
  • ApexTestQueueItem: Queued test executions
  • ApexTestResult: Test execution results
  • TraceFlag: Debug trace flags

Complete Example

import jsforce from 'jsforce';

const conn = new jsforce.Connection({
  loginUrl: 'https://login.salesforce.com'
});

await conn.login(username, password);

// Query Apex classes
const classes = await conn.tooling.query(
  "SELECT Id, Name, Body FROM ApexClass WHERE Name LIKE 'Test%' LIMIT 5"
);

console.log('Found test classes:', classes.records.length);

// Execute anonymous Apex
const executeResult = await conn.tooling.executeAnonymous(`
  System.debug('Testing anonymous Apex execution');
  Integer x = 5 + 3;
  System.debug('Result: ' + x);
`);

if (executeResult.success) {
  console.log('Anonymous Apex executed successfully');
}

// Run tests
const testResult = await conn.tooling.runTestsSynchronous({
  tests: classes.records.map(cls => ({ classId: cls.Id }))
});

console.log('\nTest Results:');
console.log('Tests run:', testResult.numTestsRun);
console.log('Failures:', testResult.numFailures);
console.log('Time:', testResult.totalTime, 'ms');

if (testResult.failures.length > 0) {
  console.log('\nFailures:');
  for (const failure of testResult.failures) {
    console.log('-', failure.name + '.' + failure.methodName);
    console.log('  ', failure.message);
  }
}

// Code coverage
console.log('\nCode Coverage:');
for (const cov of testResult.codeCoverage) {
  const percent = ((cov.numLocations - cov.numLocationsNotCovered) / cov.numLocations * 100).toFixed(1);
  console.log(`${cov.name}: ${percent}%`);
}

// Create a new Apex class
const newClass = await conn.tooling.sobject('ApexClass').create({
  Name: 'ToolingAPIExample',
  Body: `public class ToolingAPIExample {
    public static void sayHello() {
      System.debug('Hello from Tooling API!');
    }
  }`,
  ApiVersion: 58.0,
  Status: 'Active'
});

console.log('\nCreated new class:', newClass.id);

// Get completions
const completions = await conn.tooling.completions('apex');
console.log('\nAvailable namespaces:', Object.keys(completions.publicDeclarations || {}));
The Tooling API uses a separate endpoint from the standard REST API: /services/data/vXX.0/tooling

TypeScript Support

import type {
  ExecuteAnonymousResult,
  RunTestsRequest,
  RunTestsResult,
  CompletionsResult
} from 'jsforce';

interface ApexClass {
  Id: string;
  Name: string;
  Body: string;
  ApiVersion: number;
  Status: string;
}

const classes = await conn.tooling.query<ApexClass>(
  'SELECT Id, Name, Body FROM ApexClass LIMIT 10'
);

for (const cls of classes.records) {
  console.log(cls.Name); // TypeScript knows the type
}

Build docs developers (and LLMs) love