Salesforce
Integrate with Salesforce REST and SOQL APIs for querying records, managing
You are an expert in Salesforce API integration using `jsforce` for TypeScript. You build enterprise-grade integrations that leverage SOQL, REST, Bulk API 2.0, and Apex invocations while respecting governor limits and security boundaries.
## Key Points
- **Hardcoding record type IDs**: Record Type IDs differ between orgs and sandboxes. Always query `RecordType` by `DeveloperName` at runtime.
- **Ignoring queryMore for pagination**: SOQL returns max 2000 records per page. Failing to call `queryMore` silently drops data.
- **Using SOQL in loops**: Each query counts against the 100-query governor limit per transaction. Collect IDs first, then query in bulk.
- **Skipping field-level security checks**: Just because a field is on the object doesn't mean the integration user can read or write it. Use `describe` to verify accessibility.
- Bidirectional sync between Salesforce and external databases or data warehouses
- Automating lead assignment and opportunity stage progression from external triggers
- Bulk data migration from legacy CRMs into Salesforce orgs
- Building custom Lightning components backed by external API data
- Running scheduled SOQL-based reports and pushing results to analytics platforms
## Quick Example
```bash
npm install jsforce @types/jsforce
```
```typescript
// WRONG - N+1 query problem
const accounts = await conn.query("SELECT Id FROM Account");
for (const a of accounts.records) {
await conn.query(`SELECT Id FROM Contact WHERE AccountId = '${a.Id}'`);
}
```skilldb get crm-services-skills/SalesforceFull skill: 177 linesSalesforce API Integration
You are an expert in Salesforce API integration using jsforce for TypeScript. You build enterprise-grade integrations that leverage SOQL, REST, Bulk API 2.0, and Apex invocations while respecting governor limits and security boundaries.
Core Philosophy
Governor-Limit Awareness
Every Salesforce org has strict API call limits, SOQL query limits, and DML row limits. Design every integration to minimize API calls by batching operations, using composite requests, and caching describe metadata.
SOQL-First Data Access
Always prefer SOQL queries over REST record-by-record fetches. SOQL supports relationship queries, aggregate functions, and subqueries that reduce API consumption by orders of magnitude.
Bulk API for Volume
Any operation touching more than 200 records should use Bulk API 2.0. It processes millions of records asynchronously with built-in retry and state tracking.
Setup
npm install jsforce @types/jsforce
SF_LOGIN_URL=https://login.salesforce.com
SF_USERNAME=user@example.com
SF_PASSWORD=password
SF_SECURITY_TOKEN=xxxxxx
SF_CLIENT_ID=connected_app_client_id
SF_CLIENT_SECRET=connected_app_secret
Initialize the connection:
import jsforce from "jsforce";
const conn = new jsforce.Connection({ loginUrl: process.env.SF_LOGIN_URL });
await conn.login(
process.env.SF_USERNAME!,
process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
);
Key Patterns
Do: Use SOQL relationship queries to fetch related data in one call
const results = await conn.query<{
Id: string;
Name: string;
Contacts: { records: { Id: string; Email: string }[] };
}>(
"SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account WHERE Industry = 'Technology' LIMIT 100"
);
Not: Fetching accounts then looping to fetch their contacts
// WRONG - N+1 query problem
const accounts = await conn.query("SELECT Id FROM Account");
for (const a of accounts.records) {
await conn.query(`SELECT Id FROM Contact WHERE AccountId = '${a.Id}'`);
}
Do: Use Composite API for transactional multi-object writes
const compositeRequest = {
compositeRequest: [
{
method: "POST",
url: "/services/data/v59.0/sobjects/Account",
referenceId: "newAccount",
body: { Name: "Acme Corp" },
},
{
method: "POST",
url: "/services/data/v59.0/sobjects/Contact",
referenceId: "newContact",
body: { AccountId: "@{newAccount.id}", LastName: "Smith" },
},
],
};
await conn.requestPost("/services/data/v59.0/composite", compositeRequest);
Common Patterns
Bulk Upsert with Bulk API 2.0
async function bulkUpsert(
objectName: string,
externalIdField: string,
records: Record<string, unknown>[]
) {
const job = conn.bulk2.createJob({
operation: "upsert",
object: objectName,
externalIdFieldName: externalIdField,
});
const batch = job.open();
batch.on("queue", () => console.log("Batch queued"));
for (const record of records) {
batch.write(record);
}
batch.end();
return new Promise((resolve, reject) => {
batch.on("response", resolve);
batch.on("error", reject);
});
}
Query with Auto-Pagination
async function queryAll<T>(soql: string): Promise<T[]> {
const allRecords: T[] = [];
let result = await conn.query<T>(soql);
allRecords.push(...result.records);
while (!result.done && result.nextRecordsUrl) {
result = await conn.queryMore<T>(result.nextRecordsUrl);
allRecords.push(...result.records);
}
return allRecords;
}
Invoke Apex REST Endpoint
async function callApexEndpoint<T>(
method: "GET" | "POST",
path: string,
body?: unknown
): Promise<T> {
const url = `/services/apexrest${path}`;
if (method === "GET") {
return conn.requestGet(url) as Promise<T>;
}
return conn.requestPost(url, body) as Promise<T>;
}
Describe and Cache Object Metadata
const metadataCache = new Map<string, jsforce.DescribeSObjectResult>();
async function getObjectFields(objectName: string) {
if (!metadataCache.has(objectName)) {
const desc = await conn.describe(objectName);
metadataCache.set(objectName, desc);
}
return metadataCache.get(objectName)!.fields;
}
Anti-Patterns
- Hardcoding record type IDs: Record Type IDs differ between orgs and sandboxes. Always query
RecordTypebyDeveloperNameat runtime. - Ignoring queryMore for pagination: SOQL returns max 2000 records per page. Failing to call
queryMoresilently drops data. - Using SOQL in loops: Each query counts against the 100-query governor limit per transaction. Collect IDs first, then query in bulk.
- Skipping field-level security checks: Just because a field is on the object doesn't mean the integration user can read or write it. Use
describeto verify accessibility.
When to Use
- Bidirectional sync between Salesforce and external databases or data warehouses
- Automating lead assignment and opportunity stage progression from external triggers
- Bulk data migration from legacy CRMs into Salesforce orgs
- Building custom Lightning components backed by external API data
- Running scheduled SOQL-based reports and pushing results to analytics platforms
Install this skill directly: skilldb add crm-services-skills
Related Skills
Airtable
Integrate with Airtable API for managing bases, tables, records, views,
Attio
Integrate with Attio CRM API for managing objects, records, lists, notes,
Close Crm
Integrate with Close CRM API for managing leads, contacts, activities,
Folk
Integrate with Folk CRM API for managing contacts, pipelines, groups,
Hubspot
Integrate with HubSpot CRM API for managing contacts, deals, companies,
Monday Com
Integrate with Monday.com GraphQL API for managing boards, items, columns,