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
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.
Execution resultWhether the code compiled successfully
Whether the execution succeeded
Line number where error occurred (if any)
Column number where error occurred (if any)
Compilation error message
Runtime exception message
result.exceptionStackTrace
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.
Test execution requestTests to runApex class ID (use classId OR className)
Apex class name (use classId OR className)
Specific test methods to run (omit to run all)
Test level: RunSpecifiedTests, RunLocalTests, RunAllTestsInOrg
Skip code coverage calculation (default: false)
Test execution resultsFailed test methods with error details
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 requestComma-separated class IDs
Comma-separated class names
Comma-separated test suite IDs
Comma-separated test suite names
Alternative: array of test configurations
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.
Completion type (default: βapexβ)
Completion informationresult.publicDeclarations
For Apex: namespaced public class declarations with methods/properties
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));
}
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
}