salesforce-pack
Claude Code skill pack for Salesforce (30 skills)
Installation
Open Claude Code and run this command:
/plugin install salesforce-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> 30 production-grade Claude Code skills for Salesforce CRM integration — jsforce, SOQL, Bulk API 2.0, Change Data Capture, and Apex development patterns.
Skills (30)
'Apply Salesforce advanced debugging with debug logs, SOQL query plans,.
Salesforce Advanced Troubleshooting
Overview
Deep debugging techniques for complex Salesforce issues: Apex debug log analysis, SOQL query plan optimization, governor limit diagnosis, and EventLogFile forensics.
Prerequisites
- Salesforce CLI authenticated
- Access to Setup > Debug Logs
- Understanding of Apex governor limits
- Enterprise+ for EventLogFile access
Instructions
Step 1: Enable Debug Logging
# Set up debug logging for a specific user
sf apex log list --target-org my-org
# Create a trace flag for detailed logging
# Setup > Debug Logs > New Trace Flag
# - Traced Entity: your integration user
# - Debug Level: SFDC_DevConsole (or create custom)
# - Start: now, Expiration: +2 hours
# Tail logs in real-time
sf apex log tail --target-org my-org --debug-level SFDC_DevConsole
Step 2: Analyze Debug Log for Governor Limits
// Key governor limits to watch in debug logs:
const GOVERNOR_LIMITS = {
'Number of SOQL queries': { limit: 100, trigger: 'per transaction' },
'Number of query rows': { limit: 50000, trigger: 'per transaction' },
'Number of DML statements': { limit: 150, trigger: 'per transaction' },
'Number of DML rows': { limit: 10000, trigger: 'per transaction' },
'Maximum CPU time': { limit: 10000, trigger: 'ms per transaction' },
'Maximum heap size': { limit: 6000000, trigger: 'bytes (sync), 12MB (async)' },
'Number of callouts': { limit: 100, trigger: 'per transaction' },
'Number of future calls': { limit: 50, trigger: 'per transaction' },
};
// Parse debug log for limit consumption
// Look for lines like:
// Number of SOQL queries: 45 out of 100
// Number of query rows: 23456 out of 50000
// Maximum CPU time on the Salesforce servers: 8500 out of 10000
# Download and analyze debug log
sf apex log get --number 1 --target-org my-org > debug.log
# Search for limit warnings
grep -n "LIMIT_USAGE_FOR_NS" debug.log
grep -n "out of" debug.log | tail -20
# Search for slow SOQL
grep -n "SOQL_EXECUTE_BEGIN" debug.log
grep -n "SOQL_EXECUTE_END" debug.log
# Compare timestamps — queries > 1000ms need optimization
Step 3: SOQL Query Plan Analysis
// Use the Query Plan tool in Developer Console:
// Developer Console > Query Editor > Query Plan button
// Or via REST API (Tooling API)
const conn = await getConnection();
const queryPlan = await conn.request({
method: 'GET',
url: `/services/data/v59.0/query/?explain=SELECT Id, Name FROM Account WHERE Industry = 'Technology'Choose and implement Salesforce integration architecture patterns for.
Salesforce Architecture Variants
Overview
Three validated architecture blueprints for Salesforce integrations: Direct API (simple), Event-Driven (scalable), and Middleware/iPaaS (enterprise). Each pattern addresses different scale, latency, and complexity requirements.
Prerequisites
- Understanding of your data volume and sync frequency requirements
- Decision on unidirectional vs bidirectional data flow
- Knowledge of Salesforce edition (affects available features like CDC)
Variant A: Direct API Integration (Simple)
Best for: MVPs, < 50K records/day, single-direction sync
┌─────────────┐ jsforce ┌─────────────┐
│ Your App │ ──── REST API ──▶ │ Salesforce │
│ (Node.js) │ ◀── SOQL/SOSL ── │ Org │
└─────────────┘ └─────────────┘
Data flow:
- App queries SF via SOQL (polling or on-demand)
- App writes to SF via sObject CRUD
- Scheduled cron for periodic sync
Key Characteristics
- Single jsforce connection per process
- Polling-based reads (cron schedule)
- Direct REST writes
- In-memory or Redis caching for describe/metadata
- Suitable for: internal tools, admin dashboards, simple data sync
Code Pattern
// Cron-based sync — runs every 15 minutes
import cron from 'node-cron';
cron.schedule('*/15 * * * *', async () => {
const conn = await getConnection();
// Fetch recently modified accounts
const accounts = await conn.query(`
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE LastModifiedDate >= ${fifteenMinutesAgo}
`);
// Sync to local database
for (const account of accounts.records) {
await localDb.upsert('accounts', mapFromSalesforce(account));
}
});
Variant B: Event-Driven Integration (Scalable)
Best for: Real-time sync, 50K-5M records/day, bidirectional flow
┌─────────────┐ ┌─────────────┐
│ Your App │ ◀─── CDC Events ─── │ Salesforce │
│ (listener) │ Change Data Capture │ Org │
│ │ │ │
│ │ ── Bulk API 2.0 ──▶ │ │
│ │ (write-back) │ │
└──────┬──────┘ └─────────────┘
│
┌────▼────┐
│ Queue │ (Redis/SQS/Pub-Sub)
│ (async │
│ writes)│
└─────────┘
Key Characteristics
- CDC for real-time change notifications (no polling waste)
- Bulk API 2.0 for high-volume writes
- Queue-based async processing for write-back
- ReplayId tracking for event resumption
- Suitable for: CRM sync, data warehouse ETL, real-time dashboards
Code Pattern
// Event-driven — near-real-time 'Configure Salesforce CI/CD with GitHub Actions, SFDX deployments, and.
Salesforce CI Integration
Overview
Set up CI/CD pipelines for Salesforce using GitHub Actions with JWT-based authentication, automated Apex testing, and metadata deployment.
Prerequisites
- GitHub repository with Actions enabled
- Salesforce Connected App with JWT Bearer flow configured
- RSA key pair (private key stored as GitHub Secret)
- Scratch org or sandbox for test execution
Instructions
Step 1: Create GitHub Actions Workflow
Create .github/workflows/salesforce-ci.yml:
name: Salesforce CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli
- name: Authenticate to Salesforce (JWT)
run: |
echo "${{ secrets.SF_JWT_KEY }}" > server.key
sf org login jwt \
--client-id ${{ secrets.SF_CLIENT_ID }} \
--jwt-key-file server.key \
--username ${{ secrets.SF_USERNAME }} \
--set-default \
--alias ci-org
rm server.key
- name: Validate Metadata Deployment (dry run)
run: |
sf project deploy start \
--target-org ci-org \
--dry-run \
--wait 30
- name: Run Apex Tests
run: |
sf apex run test \
--target-org ci-org \
--result-format human \
--code-coverage \
--wait 20
- name: Check API Limits
run: |
sf limits api display --target-org ci-org --json | \
jq '.result[] | select(.name == "DailyApiRequests") | "\(.name): \(.remaining)/\(.max)"'
integration-tests:
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run jsforce Integration Tests
env:
SF_LOGIN_URL: https://test.salesforce.com
SF_USERNAME: ${{ secrets.SF_USERNAME }}
SF_PASSWORD: ${{ secrets.SF_PASSWORD }}
SF_SECURITY_TOKEN: ${{ secrets.SF_SECURITY_TOKEN }}
run: npm run test:integration
Step 2: Configure GitHub Secrets
# JWT private key (from your RSA key pair)
gh secret set SF_JWT_KEY < server.key
# Connected App consumer key
gh secret set SF_CLIENT_ID --body "3MVG9..."
# Integration user credentials
gh secret set SF_USERNAME --body "ci-user@yourcompany.com"
gh secret set SF_PASSWORD --body "password"
gh secret 'Diagnose and fix Salesforce common errors, SOQL issues, and API exceptions.
Salesforce Common Errors
Overview
Quick reference for the most common Salesforce API errors with real error codes, messages, and solutions.
Prerequisites
- Salesforce connection established (jsforce or simple-salesforce)
- Access to Setup in your Salesforce org
- Familiarity with sObject field API names
Instructions
Step 1: Identify the Error
Check the errorCode field in the API response or the exception from jsforce.
Step 2: Match to Error Below
INVALID_LOGIN — Authentication Failed
[{"message":"INVALID_LOGIN: Invalid username, password, security token; or user locked out.","errorCode":"INVALID_LOGIN"}]
Cause: Wrong credentials or security token.
Solution:
# Reset security token: Setup > My Personal Information > Reset My Security Token
# Append token to password: password + securityToken
# Verify IP is whitelisted or token is appended
echo "Password format: ${SF_PASSWORD}${SF_SECURITY_TOKEN}"
INVALID_FIELD — Wrong Field Name in SOQL
[{"message":"SELECT Id, FullName FROM Account\n ^\nERROR: No such column 'FullName' on entity 'Account'","errorCode":"INVALID_FIELD"}]
Cause: Field API name does not exist on the sObject.
Solution:
// Check available fields via describe
const meta = await conn.sobject('Account').describe();
const fieldNames = meta.fields.map(f => f.name);
console.log('Available fields:', fieldNames.join(', '));
// Common mistake: "FullName" vs "Name", "Email" on Account (doesn't exist — it's on Contact)
MALFORMED_QUERY — SOQL Syntax Error
[{"message":"unexpected token: 'FORM'","errorCode":"MALFORMED_QUERY"}]
Cause: Typo in SOQL keywords or missing quotes.
Solution:
-- Wrong: SELECT Id FORM Account (typo)
-- Right: SELECT Id FROM Account
-- Wrong: WHERE Name = Acme (missing quotes)
-- Right: WHERE Name = 'Acme'
-- Wrong: WHERE CreatedDate > 2026-01-01 (needs literal format)
-- Right: WHERE CreatedDate > 2026-01-01T00:00:00Z
REQUIREDFIELDMISSING — Missing Required Fields on Create
[{"message":"Required fields are missing: [LastName]","errorCode":"REQUIRED_FIELD_MISSING","fields":["LastName"]}]
Cause: Create/update missing a required field.
S
'Execute Salesforce CRUD operations on standard sObjects with SOQL and.
Salesforce Core Workflow A — CRUD & SOQL
Overview
Primary workflow: perform CRUD operations on standard sObjects (Account, Contact, Lead, Opportunity) using SOQL queries, jsforce methods, and REST API endpoints.
Prerequisites
- Completed
salesforce-install-authsetup - jsforce installed and connection configured
- Understanding of Salesforce sObject model
Instructions
Step 1: SOQL Queries (Read Operations)
import { getConnection } from './salesforce/connection';
const conn = await getConnection();
// Basic query — all open opportunities closing this quarter
const opps = await conn.query(`
SELECT Id, Name, Amount, StageName, CloseDate, Account.Name
FROM Opportunity
WHERE IsClosed = false
AND CloseDate = THIS_QUARTER
ORDER BY Amount DESC
LIMIT 50
`);
// Relationship query — Accounts with their Contacts
const accountsWithContacts = await conn.query(`
SELECT Id, Name, Industry,
(SELECT Id, FirstName, LastName, Email FROM Contacts)
FROM Account
WHERE Industry = 'Technology'
LIMIT 20
`);
// Aggregate query
const revByIndustry = await conn.query(`
SELECT Industry, COUNT(Id) numAccounts, SUM(AnnualRevenue) totalRevenue
FROM Account
WHERE Industry != null
GROUP BY Industry
ORDER BY SUM(AnnualRevenue) DESC
`);
// SOSL search — full-text search across objects
const searchResults = await conn.search(
`FIND {Acme} IN ALL FIELDS RETURNING
Account(Id, Name, Industry),
Contact(Id, FirstName, LastName, Email),
Lead(Id, Name, Company, Status)`
);
Step 2: Create Records
// Single record create via sObject
const newLead = await conn.sobject('Lead').create({
FirstName: 'Jane',
LastName: 'Smith',
Company: 'Acme Corp',
Email: 'jane.smith@acme.example.com',
Status: 'Open - Not Contacted',
LeadSource: 'Web',
});
console.log('Lead ID:', newLead.id); // '00Qxx...'
// Create with relationship (Contact linked to Account)
const newContact = await conn.sobject('Contact').create({
FirstName: 'John',
LastName: 'Doe',
Email: 'john.doe@example.com',
AccountId: '001xxxxxxxxxxxx', // Existing Account ID
Title: 'VP Engineering',
});
// Create Opportunity with all key fields
const newOpp = await conn.sobject('Opportunity').create({
Name: 'Acme Corp — Enterprise License',
AccountId: '001xxxxxxxxxxxx',
StageName: 'Prospecting',
CloseDate: '2026-06-30',
Amount: 50000,
});
Step 3: Bulk Create with sObject Collections
// Create up to 200 records in a single API call (sObject Collections)
const contacts = [
{ FirstName: 'Alice', LastName: 'A', Email: 'alice@example.'Execute Salesforce Bulk API 2.
Salesforce Core Workflow B — Bulk & Composite API
Overview
High-volume data operations using Bulk API 2.0 (millions of records) and Composite API (multi-step transactions in a single call).
Prerequisites
- Completed
salesforce-install-authsetup - Understanding of
salesforce-core-workflow-a(standard CRUD) - jsforce installed with connection configured
Instructions
Step 1: Bulk API 2.0 — Ingest (Insert/Update/Upsert/Delete)
import { getConnection } from './salesforce/connection';
import fs from 'fs';
const conn = await getConnection();
// Create a bulk ingest job
const job = conn.bulk2.createJob({
operation: 'insert', // insert | update | upsert | delete
object: 'Contact',
// For upsert, specify: externalIdFieldName: 'External_ID__c'
});
// Upload CSV data
const csvData = `FirstName,LastName,Email,AccountId
Alice,Johnson,alice@example.com,001xxxxxxxxxxxx
Bob,Williams,bob@example.com,001xxxxxxxxxxxx
Carol,Davis,carol@example.com,001xxxxxxxxxxxx`;
// jsforce handles chunking automatically
const results = await conn.bulk2.loadAndWaitForResults({
object: 'Contact',
operation: 'insert',
input: csvData,
});
console.log('Successful:', results.successfulResults.length);
console.log('Failed:', results.failedResults.length);
for (const failure of results.failedResults) {
console.error(`Row ${failure.sf__Id}: ${failure.sf__Error}`);
}
Step 2: Bulk API 2.0 — Query (Export)
// Bulk query for large datasets (100K+ records)
const queryResults = await conn.bulk2.query(
`SELECT Id, Name, Email, Account.Name
FROM Contact
WHERE CreatedDate >= LAST_N_DAYS:90`
);
// Stream results for memory efficiency
let recordCount = 0;
for await (const record of queryResults) {
recordCount++;
// Process each record
console.log(`${record.Name} — ${record.Email}`);
}
console.log(`Total exported: ${recordCount}`);
Step 3: Bulk API 2.0 — File-Based Upload
// Upload from a CSV file (for very large datasets)
const csvStream = fs.createReadStream('contacts-import.csv');
const bulkResults = await conn.bulk2.loadAndWaitForResults({
object: 'Contact',
operation: 'upsert',
externalIdFieldName: 'External_ID__c',
input: csvStream,
pollTimeout: 600000, // 10 min timeout for large jobs
pollInterval: 5000, // Check every 5 seconds
});
console.log(`Processed: ${bulkResults.successfulResults.length} success, ${bulkResults.failedResults.length} failed`);
Step 4: Composite API — Multiple Operations in One Call
// Execute up to 25 subrequests in a single API call
// Each subrequest counts as a separate API call for limits
con'Optimize Salesforce costs through API call reduction, edition selection,.
Salesforce Cost Tuning
Overview
Optimize Salesforce costs by reducing API call consumption, choosing the right edition, and monitoring API usage budgets. Salesforce charges per-user licenses (not per-API-call), but API limits are tied to edition + license count.
Prerequisites
- Access to Salesforce Setup > Company Information
- Understanding of current API usage patterns
- Access to contract/license details
Instructions
Step 1: Understand Salesforce Pricing Model
| Edition | Per-User/Month | API Calls/Day (Base) | Per-User API Calls |
|---|---|---|---|
| Developer | Free | 15,000 | N/A (1 user) |
| Essentials | ~$25 | 15,000 | +1,000/user |
| Professional | ~$80 | 15,000 | +1,000/user |
| Enterprise | ~$165 | 100,000 | +1,000/user |
| Unlimited | ~$330 | 100,000 | +5,000/user |
| API Add-on Pack | Varies | +200K-10M/day | Per org |
Key insight: API calls are per-org, not per-user. A 50-user Enterprise org gets 100,000 + (50 * 1,000) = 150,000 daily API calls. All integrations share this pool.
Step 2: Monitor Current Usage
const conn = await getConnection();
const limits = await conn.request('/services/data/v59.0/limits/');
const apiUsage = {
daily: {
used: limits.DailyApiRequests.Max - limits.DailyApiRequests.Remaining,
remaining: limits.DailyApiRequests.Remaining,
max: limits.DailyApiRequests.Max,
percentUsed: ((limits.DailyApiRequests.Max - limits.DailyApiRequests.Remaining) / limits.DailyApiRequests.Max * 100).toFixed(1),
},
bulk: {
ingestJobs: limits.DailyBulkV2QueryJobs,
queryJobs: limits.DailyBulkV2QueryJobs,
},
storage: {
dataMB: `${limits.DataStorageMB.Max - limits.DataStorageMB.Remaining}/${limits.DataStorageMB.Max} MB`,
fileMB: `${limits.FileStorageMB.Max - limits.FileStorageMB.Remaining}/${limits.FileStorageMB.Max} MB`,
},
};
console.log('API Usage:', JSON.stringify(apiUsage, null, 2));
Step 3: Reduce API Call Count (Biggest Cost Lever)
// BEFORE: 1 API call per record = expensive
for (const contact of contacts) {
await conn.sobject('Contact').create(contact); // 1000 calls for 1000 records
}
// AFTER: Batch with sObject Collections = 5 calls for 1000 records
for (let i = 0; i < contacts.length; i += 200) {
const batch = contacts.slice(i, i + 200);
await conn.sobject('Contact').create(batch); // Max 200 per call
}
// AFTER: Use Bulk API for 10K+ records = 1 job regardless of count
await conn.bulk2.loadAndWaitForResults({
object: 'Contact',
operation'Implement Salesforce data privacy, GDPR/CCPA compliance, and field-level.
Salesforce Data Handling
Overview
Handle sensitive data correctly when integrating with Salesforce: PII classification, GDPR/CCPA compliance with Salesforce's Individual object, data retention, and field-level encryption.
Prerequisites
- Understanding of GDPR/CCPA requirements
- Salesforce org with data classification enabled (Setup > Data Classification)
- For encryption: Salesforce Shield license (Platform Encryption)
Instructions
Step 1: Salesforce Data Classification
// Salesforce has built-in data classification on fields
// Setup > Object Manager > [Object] > Fields > [Field] > Edit > Data Sensitivity Level
// Query field classification metadata
const conn = await getConnection();
const contactMeta = await conn.sobject('Contact').describe();
const sensitiveFields = contactMeta.fields
.filter((f: any) => f.compoundFieldName === null) // Skip compound fields
.map((f: any) => ({
name: f.name,
label: f.label,
type: f.type,
encrypted: f.encrypted || false,
// Check custom data sensitivity via field metadata
}));
// PII fields in standard Salesforce objects
const PII_FIELDS: Record<string, string[]> = {
Contact: ['FirstName', 'LastName', 'Email', 'Phone', 'MailingAddress', 'Birthdate'],
Lead: ['FirstName', 'LastName', 'Email', 'Phone', 'Company'],
Account: ['Phone', 'Website'], // Less PII, but may contain it
User: ['Email', 'Phone', 'Username'],
Case: ['SuppliedEmail', 'SuppliedName', 'SuppliedPhone'],
};
Step 2: GDPR — Individual Object & Consent
// Salesforce has a built-in "Individual" object for GDPR consent tracking
// Setup > Data Protection and Privacy > Enable Individual object
// Link Contact to Individual for consent tracking
await conn.sobject('Individual').create({
FirstName: 'Jane',
LastName: 'Smith',
HasOptedOutTracking: false,
HasOptedOutProcessing: false,
HasOptedOutSolicit: true,
});
// Check consent before processing
const contact = await conn.query(`
SELECT Id, FirstName, LastName, Email,
Individual.HasOptedOutTracking,
Individual.HasOptedOutProcessing
FROM Contact
WHERE Id = '003xxxxxxxxxxxx'
`);
const individual = contact.records[0]?.Individual;
if (individual?.HasOptedOutProcessing) {
console.log('Contact has opted out of data processing — skip');
}
Step 3: Data Subject Access Request (DSAR)
// GDPR Article 15: Right of Access
async function exportContactData(contactId: string): Promise<object> {
const conn = await getConnection();
// Gather all data related to this contact
'Collect Salesforce debug evidence including API limits, debug logs,.
Salesforce Debug Bundle
Overview
Collect all necessary diagnostic information for Salesforce issues: debug logs, API limits, org configuration, and error traces.
Prerequisites
- Salesforce CLI authenticated (
sf org login web) - jsforce connection configured
- Access to Setup in your Salesforce org
Instructions
Step 1: Collect Org Info & API Limits
import { getConnection } from './salesforce/connection';
const conn = await getConnection();
// Org limits — most critical diagnostic info
const limits = await conn.request('/services/data/v59.0/limits/');
console.log('=== API Limits ===');
console.log(`Daily API Requests: ${limits.DailyApiRequests.Remaining}/${limits.DailyApiRequests.Max}`);
console.log(`Daily Bulk API: ${limits.DailyBulkV2QueryJobs.Remaining}/${limits.DailyBulkV2QueryJobs.Max}`);
console.log(`Data Storage (MB): ${limits.DataStorageMB.Remaining}/${limits.DataStorageMB.Max}`);
console.log(`File Storage (MB): ${limits.FileStorageMB.Remaining}/${limits.FileStorageMB.Max}`);
console.log(`Single Email: ${limits.SingleEmail.Remaining}/${limits.SingleEmail.Max}`);
// Org identity
const identity = await conn.identity();
console.log(`\n=== Org Info ===`);
console.log(`Username: ${identity.username}`);
console.log(`Org ID: ${identity.organization_id}`);
console.log(`Instance: ${conn.instanceUrl}`);
console.log(`API Version: ${conn.version}`);
Step 2: Enable & Retrieve Debug Logs
# Set up a trace flag for debug logging via SF CLI
sf apex log list --target-org my-org
# Get the most recent debug log
sf apex log get --number 1 --target-org my-org
# Or tail logs in real-time during testing
sf apex log tail --target-org my-org --debug-level SFDC_DevConsole
Step 3: Query Recent API Events
// EventLogFile — Enterprise+ orgs only
// Contains API usage data for the last 30 days
const eventLogs = await conn.query(`
SELECT Id, EventType, LogDate, LogFileLength
FROM EventLogFile
WHERE EventType = 'API'
AND LogDate >= LAST_N_DAYS:7
ORDER BY LogDate DESC
LIMIT 5
`);
for (const log of eventLogs.records) {
console.log(`Event: ${log.EventType}, Date: ${log.LogDate}, Size: ${log.LogFileLength}`);
// Download log content
const content = await conn.request(`/services/data/v59.0/sobjects/EventLogFile/${log.Id}/LogFile`);
console.log(content);
}
Step 4: Create Debug Bundle Script
#!/bin/bash
# salesforce-debug-bundle.sh
BUNDLE_DIR="sf-debug-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE_DIR"
echo "=== Salesforce Debug Bundle ===" > "$BUNDLE_DIR/summary.txt"
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$BUNDLE_DIR/summary.txt"
# Org info
sf org d'Deploy Salesforce-connected applications to Heroku, Vercel, and Cloud.
Salesforce Deploy Integration
Overview
Deploy Salesforce-connected Node.js applications to Heroku (native SF integration), Vercel, or Cloud Run with JWT authentication and proper secrets management.
Prerequisites
- Salesforce Connected App with JWT Bearer flow
- RSA key pair for server-to-server auth
- Platform CLI installed (heroku, vercel, or gcloud)
- Application tested against sandbox
Instructions
Heroku Deployment (Recommended for Salesforce)
Heroku has native Salesforce integration via Heroku Connect (bi-directional data sync).
# Create Heroku app
heroku create my-sf-app
# Set Salesforce credentials as config vars
heroku config:set SF_LOGIN_URL=https://login.salesforce.com
heroku config:set SF_CLIENT_ID=3MVG9...
heroku config:set SF_USERNAME=integration@yourcompany.com
heroku config:set SF_JWT_KEY="$(cat server.key)"
# Deploy
git push heroku main
# Optional: Add Heroku Connect for bi-directional sync
heroku addons:create herokuconnect:demo
heroku connect:authorize
# Map sObjects: Account, Contact, Opportunity → Postgres tables
Vercel Deployment (Serverless)
# Add Salesforce secrets
vercel env add SF_LOGIN_URL production
vercel env add SF_CLIENT_ID production
vercel env add SF_USERNAME production
vercel env add SF_JWT_KEY production # Paste private key content
# Deploy
vercel --prod
{
"functions": {
"api/**/*.ts": {
"maxDuration": 30
}
}
}
// api/salesforce/accounts.ts — Vercel serverless function
import jsforce from 'jsforce';
export default async function handler(req, res) {
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL,
});
// JWT auth — no password needed
await conn.authorize({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: process.env.SF_CLIENT_ID!,
username: process.env.SF_USERNAME!,
privateKey: process.env.SF_JWT_KEY!,
});
const accounts = await conn.query(
'SELECT Id, Name, Industry FROM Account ORDER BY CreatedDate DESC LIMIT 10'
);
res.json({ accounts: accounts.records });
}
Google Cloud Run
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]
# Store JWT key in Secret Manager
echo -n "$(cat server.key)" | gcloud secrets create sf-jwt-key --data-file=-
# Build and deploy
gcloud builds submit --tag gcr.io/$PROJECT_ID/sf-service
gcloud run deploy sf-service \
--image gcr.io/$PROJECT_ID/sf-service \
--region us-central1 \
--platform managed \
--set-env-vars SF_LOGIN_URL=https://login.salesforce.'Configure Salesforce Profiles, Permission Sets, and Sharing Rules for.
Salesforce Enterprise RBAC
Overview
Configure Salesforce's multi-layered security model: Profiles (baseline permissions), Permission Sets (additive permissions), Roles (record visibility), Organization-Wide Defaults (OWD), and Sharing Rules.
Prerequisites
- Salesforce System Administrator access
- Understanding of your org's user hierarchy
- For SSO: Identity Provider (Okta, Azure AD, etc.) with SAML 2.0
Instructions
Step 1: Understand the Salesforce Security Model
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: Organization-Wide Defaults (OWD) │
│ Most restrictive baseline — controls default record access │
│ Options: Private | Public Read Only | Public Read/Write │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: Role Hierarchy │
│ Users above in hierarchy see records of users below │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: Sharing Rules │
│ Grant access to groups of users based on criteria or ownership │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: Manual Sharing │
│ Record owner shares individual records │
├─────────────────────────────────────────────────────────────────┤
│ Layer 5: Profile / Permission Set │
│ Controls CRUD + FLS (which objects and fields users can access) │
└─────────────────────────────────────────────────────────────────┘
Key principle: Salesforce OPENS access, never restricts beyond OWD.
OWD sets the floor. Everything else ADDS access on top.
Step 2: Configure Profiles for Integration Users
// Query existing profiles
const conn = await getConnection();
const profiles = await conn.query(`
SELECT Id, Name, UserType, Description
FROM Profile
WHERE UserType = 'Standard'
ORDER BY Name
`);
// Best practice: Create a custom profile for API integrations
// Setup > Profiles > Clone "Standard User" > Name: "API Integration Profile"
// Set object permissions:
const integrationPermissions = {
Account: { read: true, create: true, edit: true, delete: false },
Contact: { read: true, create: true, edit: true, delete: false },
Opportunity: { read: true, create: false, edit: false, delete: false },
Lead: { read: true, create: true, edit: true, delete: false },
Case: { read: true, create: true, edit: true, delete: false },
};
// Check a user's effective permissions via API
const userPermissions = await conn.query(`
SELECT Id, Name, Profile.Name,
(SELECT PermissionSet.Name FROM PermissionSetAssignments)
FROM User
WHERE Username = 'integratio'Create a minimal working Salesforce example with SOQL queries and sObject.
Salesforce Hello World
Overview
Minimal working example: connect to Salesforce, run a SOQL query, and perform basic CRUD on standard sObjects (Account, Contact, Lead).
Prerequisites
- Completed
salesforce-install-authsetup - jsforce installed (
npm install jsforce) - Valid credentials in environment variables
Instructions
Step 1: Connect and Query Accounts
import jsforce from 'jsforce';
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL || 'https://login.salesforce.com',
});
await conn.login(
process.env.SF_USERNAME!,
process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
);
// Your first SOQL query — fetch 5 Accounts
const result = await conn.query(
"SELECT Id, Name, Industry, AnnualRevenue FROM Account LIMIT 5"
);
console.log(`Total records: ${result.totalSize}`);
for (const account of result.records) {
console.log(` ${account.Name} — ${account.Industry ?? 'N/A'}`);
}
Step 2: Create a Record
// Create a new Account
const newAccount = await conn.sobject('Account').create({
Name: 'Acme Corporation',
Industry: 'Technology',
Website: 'https://acme.example.com',
NumberOfEmployees: 250,
});
console.log('Created Account ID:', newAccount.id);
console.log('Success:', newAccount.success);
Step 3: Read a Record by ID
// Retrieve specific fields by record ID
const account = await conn.sobject('Account').retrieve(newAccount.id);
console.log('Account Name:', account.Name);
// Or use SOQL for more control
const result = await conn.query(
`SELECT Id, Name, Industry, CreatedDate
FROM Account
WHERE Id = '${newAccount.id}'`
);
Step 4: Update a Record
const updateResult = await conn.sobject('Account').update({
Id: newAccount.id,
Industry: 'Software',
Description: 'Updated via jsforce API',
});
console.log('Updated:', updateResult.success);
Step 5: Delete a Record
const deleteResult = await conn.sobject('Account').destroy(newAccount.id);
console.log('Deleted:', deleteResult.success);
Python Example
from simple_salesforce import Salesforce
import os
sf = Salesforce(
username=os.environ['SF_USERNAME'],
password=os.environ['SF_PASSWORD'],
security_token=os.environ['SF_SECURITY_TOKEN']
)
# SOQL query
result = sf.query("SELECT Id, Name, Industry FROM Account LIMIT 5")
for record in result['records']:
print(f" {record['Name']} — {record.get('Industry', &'Execute Salesforce incident response procedures with triage, mitigation,.
Salesforce Incident Runbook
Overview
Rapid incident response procedures for Salesforce integration failures, covering Salesforce-side outages, API limit exhaustion, authentication failures, and data sync issues.
Prerequisites
- Salesforce CLI authenticated (
sf org login) - Access to Salesforce Status API
- Monitoring dashboards configured (see
salesforce-observability) - Communication channels (Slack, PagerDuty)
Quick Triage (Do This First)
# 1. Is Salesforce itself down?
curl -s https://api.status.salesforce.com/v1/incidents/active | jq '.[0:3]'
# If incidents returned → Salesforce-side issue, enable fallback mode
# 2. Check your org's instance status
# Find your instance at: https://status.salesforce.com
curl -s "https://api.status.salesforce.com/v1/instances/NA45/status" | jq '.status'
# 3. Check API limits — are we out of calls?
sf limits api display --target-org my-org --json | jq '.result[] | select(.name == "DailyApiRequests")'
# If remaining = 0 → API_LIMIT_EXCEEDED, see mitigation below
# 4. Check authentication
sf org display --target-org my-org --json | jq '.result.connectedStatus'
# If "RefreshTokenError" → re-authenticate
# 5. Check recent errors in your logs
sf apex log list --target-org my-org --json | jq '.result[0:5]'
Decision Tree
Integration returning errors?
├── YES: Is status.salesforce.com showing incident?
│ ├── YES → Salesforce outage. Enable fallback mode. Monitor status page.
│ └── NO → Check error type below:
│ ├── INVALID_SESSION_ID (401) → Token expired. Re-authenticate.
│ ├── REQUEST_LIMIT_EXCEEDED (403) → API limit hit. Reduce calls.
│ ├── UNABLE_TO_LOCK_ROW (409) → Record contention. Retry with backoff.
│ ├── MALFORMED_QUERY / INVALID_FIELD → Code bug. Check SOQL.
│ └── 500/503 → Salesforce-side. Wait and retry.
└── NO: Is data syncing correctly?
├── YES → Likely resolved or intermittent. Monitor.
└── NO → Check CDC subscription, query timestamps, bulk job status.
Immediate Actions by Error Type
REQUESTLIMITEXCEEDED — API Limit Exhausted
// This is a P1 — your integration is completely blocked
// 1. Check what's consuming API calls
const limits = await conn.request('/services/data/v59.0/limits/');
console.log('API calls:', limits.DailyApiRequests);
console.log('Bulk API:', limits.DailyBulkV2QueryJobs);
// Limits reset on a 24-hour rolling basis
// 2. Identify top consumers (Enterprise+ orgs with EventLogFile)
const topUsers = await conn.query(`
SELECT UserId, COUNT(Id) callCount
FROM EventLogFile
WHERE EventType = 'API' AND LogDate = TODAY
GROUP BY UserId
ORDER BY COUNT(Id) DESC
LIMIT 10
`);
// 3. Immedi'Install and configure Salesforce SDK/CLI authentication with jsforce.
Salesforce Install & Auth
Overview
Set up Salesforce connectivity using jsforce (Node.js) or simple-salesforce (Python), and configure one of three OAuth 2.0 authentication flows.
Prerequisites
- Node.js 18+ or Python 3.10+
- A Salesforce org (Developer Edition free at developer.salesforce.com)
- Connected App configured in Setup > App Manager > New Connected App
- OAuth scopes:
api,refreshtoken,offlineaccess
Instructions
Step 1: Install SDK
# Node.js — jsforce (most popular SF client, 3M+ weekly downloads)
npm install jsforce
# Python — simple-salesforce
pip install simple-salesforce
# Salesforce CLI (for metadata, deployment, scratch orgs)
npm install -g @salesforce/cli
Step 2: Choose Authentication Flow
| Flow | Use Case | Requires Browser? |
|---|---|---|
| Username-Password | Dev/test scripts | No |
| JWT Bearer | CI/CD, server-to-server | No |
| Web Server (Authorization Code) | User-facing apps | Yes |
Step 3: Configure Credentials
# .env (NEVER commit — add .env to .gitignore)
SF_LOGIN_URL=https://login.salesforce.com
SF_USERNAME=user@example.com
SF_PASSWORD=yourpassword
SF_SECURITY_TOKEN=yourtoken
SF_CLIENT_ID=your_connected_app_consumer_key
SF_CLIENT_SECRET=your_connected_app_consumer_secret
# For sandbox orgs, use:
# SF_LOGIN_URL=https://test.salesforce.com
Step 4: Connect with Username-Password Flow
import jsforce from 'jsforce';
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL || 'https://login.salesforce.com',
});
await conn.login(
process.env.SF_USERNAME!,
process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
);
console.log('Connected to:', conn.instanceUrl);
console.log('User ID:', conn.userInfo?.id);
console.log('Org ID:', conn.userInfo?.organizationId);
Step 5: Connect with JWT Bearer Flow (Production)
import jsforce from 'jsforce';
import fs from 'fs';
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL,
// JWT requires a Connected App with a digital certificate
});
await conn.authorize({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: process.env.SF_CLIENT_ID!,
username: process.env.SF_USERNAME!,
privateKeyFile: './server.key', // RSA private key from your certificate
});
Step 6: Connect with OAuth2 Web Server Flow
import jsforce from 'jsforce';
const oauth2 = new jsforce.OAuth2({
loginUrl: process.env.S'Identify and avoid Salesforce anti-patterns including SOQL N+1, governor.
Salesforce Known Pitfalls
Overview
The 10 most common and costly mistakes when integrating with Salesforce, with real error messages and correct patterns.
Pitfall #1: SOQL N+1 Query Pattern (Most Common)
Anti-Pattern
// Query accounts, then query contacts for each = N+1 API calls
const accounts = await conn.query('SELECT Id, Name FROM Account LIMIT 100');
for (const account of accounts.records) {
// 100 extra API calls! (plus 100 extra SOQL queries in Apex)
const contacts = await conn.query(
`SELECT Id, Name, Email FROM Contact WHERE AccountId = '${account.Id}'`
);
}
// Total: 101 API calls for what should be 1
Correct Pattern
// Single relationship query — 1 API call
const accounts = await conn.query(`
SELECT Id, Name,
(SELECT Id, FirstName, LastName, Email FROM Contacts)
FROM Account
LIMIT 100
`);
// accounts.records[0].Contacts.records → child contacts
Pitfall #2: Ignoring API Limits (Org-Wide Shared Pool)
Anti-Pattern
// This integration uses 80,000 API calls/day
// Sales team uses 60,000/day
// Total: 140,000 > 150,000 limit → everyone gets blocked
Correct Pattern
// 1. Check limits before batch operations
const limits = await conn.request('/services/data/v59.0/limits/');
if (limits.DailyApiRequests.Remaining < estimatedCalls) {
throw new Error('Insufficient API calls remaining');
}
// 2. Use sObject Collections (1 call = 200 records)
await conn.sobject('Contact').create(contacts); // batch of up to 200
// 3. Use Bulk API for 10K+ (separate limit pool)
await conn.bulk2.loadAndWaitForResults({ object: 'Contact', operation: 'insert', input: csv });
Pitfall #3: SOQL Injection
Anti-Pattern
// User input directly in SOQL — injectable
const name = req.query.name; // Could be: "'; SELECT Id FROM User; --"
await conn.query(`SELECT Id FROM Account WHERE Name = '${name}'`);
Correct Pattern
function escapeSoql(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}
await conn.query(`SELECT Id FROM Account WHERE Name = '${escapeSoql(name)}'`);
Pitfall #4: SELECT FIELDS(ALL) (Performance Killer)
Anti-Pattern
// Fetches ALL fields — 200+ columns on Account, massive payload
const result = await conn.query('SELECT FIELDS(ALL) FROM Account LIMIT 100');
Correct Pattern
// Select only what you need — 5-10x faster, much less data transfer
const result = await conn.que'Implement Salesforce load testing, API limit capacity planning, and.
Salesforce Load & Scale
Overview
Load testing, scaling strategies, and capacity planning for Salesforce integrations. Focus on API limit budgeting, Bulk API throughput, and handling Salesforce's unique constraint: org-wide shared limits.
Prerequisites
- k6 or Artillery load testing tool
- Sandbox or Developer org for testing (never load test production)
- Understanding of your org's API limit allocation
- Monitoring configured (see
salesforce-observability)
Instructions
Step 1: Calculate API Limit Budget
const conn = await getConnection();
const limits = await conn.request('/services/data/v59.0/limits/');
const budget = {
dailyMax: limits.DailyApiRequests.Max,
currentlyUsed: limits.DailyApiRequests.Max - limits.DailyApiRequests.Remaining,
remaining: limits.DailyApiRequests.Remaining,
// Budget allocation
integrationA: Math.floor(limits.DailyApiRequests.Max * 0.40), // 40% for primary sync
integrationB: Math.floor(limits.DailyApiRequests.Max * 0.20), // 20% for secondary
salesUsers: Math.floor(limits.DailyApiRequests.Max * 0.30), // 30% for Salesforce UI users
headroom: Math.floor(limits.DailyApiRequests.Max * 0.10), // 10% buffer
};
console.table(budget);
// Example (Enterprise, 50 users): 150,000 daily calls
// Integration A: 60,000 | Integration B: 30,000 | Users: 45,000 | Buffer: 15,000
Step 2: Load Test with k6 (against Sandbox)
// salesforce-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
const SF_TOKEN = __ENV.SF_ACCESS_TOKEN;
const SF_INSTANCE = __ENV.SF_INSTANCE_URL;
export const options = {
stages: [
{ duration: '1m', target: 5 }, // Ramp up
{ duration: '3m', target: 5 }, // Steady state
{ duration: '1m', target: 20 }, // Peak load
{ duration: '3m', target: 20 }, // Sustained peak
{ duration: '1m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<3000'], // SF API calls are slower than typical SaaS
http_req_failed: ['rate<0.01'],
},
};
export default function () {
// SOQL query
const queryRes = http.get(
`${SF_INSTANCE}/services/data/v59.0/query/?q=SELECT+Id,Name+FROM+Account+LIMIT+10`,
{ headers: { Authorization: `Bearer ${SF_TOKEN}` } }
);
check(queryRes, { 'query 200': (r) => r.status === 200 });
// sObject retrieve
const retrieveRes = http.get(
`${SF_INSTANCE}/services/data/v59.0/sobjects/Account/describe`,
{ headers: { Authorization: `Bearer ${SF_TOKEN}` } }
);
check(retrieveRes, { 'describe 200': (r) => r.status === 200 });
// Check rate limit headers
const limitInfo = queryRes.headers['Sforce-Limit-Info'];
if (limitInfo) {
const [used, max] = limitInfo.repl'Configure Salesforce local development with scratch orgs, SFDX, and.
Salesforce Local Dev Loop
Overview
Set up a fast, reproducible local development workflow using Salesforce CLI (sf), scratch orgs, and jsforce with hot reload.
Prerequisites
- Completed
salesforce-install-authsetup - Salesforce CLI installed (
npm install -g @salesforce/cli) - Dev Hub enabled in your production org (Setup > Dev Hub)
- Node.js 18+ with npm/pnpm
Instructions
Step 1: Create SFDX Project Structure
# Initialize a new SFDX project
sf project generate --name my-sf-project --template standard
# Project structure created:
# my-sf-project/
# ├── config/
# │ └── project-scratch-def.json # Scratch org definition
# ├── force-app/
# │ └── main/default/ # Metadata source (Apex, LWC, etc.)
# ├── scripts/
# │ └── apex/ # Anonymous Apex scripts
# ├── sfdx-project.json # Project config
# └── .sf/ # Local CLI state
Step 2: Create a Scratch Org
# Authenticate to your Dev Hub first
sf org login web --set-default-dev-hub --alias DevHub
# Create a scratch org (expires in 7 days by default)
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias my-scratch \
--duration-days 7 \
--set-default
# Open scratch org in browser
sf org open --target-org my-scratch
Step 3: Configure scratch-def for development
{
"orgName": "My Dev Org",
"edition": "Developer",
"features": ["EnableSetPasswordInApi", "MultiCurrency"],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
},
"securitySettings": {
"passwordPolicies": {
"enableSetPasswordInApi": true
}
}
}
}
Step 4: Node.js Integration Dev Loop
my-integration/
├── src/
│ ├── salesforce/
│ │ ├── connection.ts # jsforce connection wrapper
│ │ ├── accounts.ts # Account operations
│ │ ├── contacts.ts # Contact operations
│ │ └── queries.ts # SOQL query builders
│ └── index.ts
├── tests/
│ ├── unit/
│ │ └── queries.test.ts # Mock-based tests
│ └── integration/
│ └── accounts.test.ts # Live org tests
├── .env.local # Local secrets (git-ignored)
├── .env.example # Template for team
└── package.json
Step 5: Configure Hot Reload
{
"scripts": {
"dev": "tsx watch src/index.ts",
"test": "vitest",
"test:watch": "vitest --watch",
"test:integration": "SF_ENV=scratch vitest run tests/integration/'Execute Salesforce data migrations using Bulk API, Data Loader, and.
Salesforce Migration Deep Dive
Overview
Comprehensive guide for migrating data to/from Salesforce: ETL patterns using Bulk API 2.0, data mapping between CRM schemas, record relationship preservation, and validation.
Prerequisites
- Source and target Salesforce orgs (or external CRM)
- jsforce with Bulk API 2.0 access
- Understanding of sObject relationships and External IDs
- Staging sandbox for dry runs
Migration Types
| Type | Complexity | Duration | Tool |
|---|---|---|---|
| CSV import (< 50K records) | Low | Hours | Data Import Wizard / Bulk API |
| CRM-to-Salesforce | Medium | Weeks | Custom ETL with jsforce |
| Org-to-org migration | Medium | Weeks | SFDX + Bulk API |
| Full re-platform | High | Months | Custom ETL + change management |
Instructions
Step 1: Data Assessment
const conn = await getConnection();
// Count records per object
const objectCounts = await Promise.all(
['Account', 'Contact', 'Lead', 'Opportunity', 'Case'].map(async (obj) => {
const result = await conn.query(`SELECT COUNT(Id) total FROM ${obj}`);
return { object: obj, count: result.records[0].total };
})
);
console.table(objectCounts);
// Account: 15,234
// Contact: 45,678
// Lead: 23,456
// Opportunity: 8,901
// Case: 67,890
// Check data storage limits
const limits = await conn.request('/services/data/v59.0/limits/');
console.log(`Data storage: ${limits.DataStorageMB.Max - limits.DataStorageMB.Remaining}/${limits.DataStorageMB.Max} MB`);
Step 2: Schema Mapping
// Map source fields to Salesforce sObject fields
interface FieldMapping {
source: string;
target: string;
transform?: (value: any) => any;
required: boolean;
}
const accountMappings: FieldMapping[] = [
{ source: 'company_name', target: 'Name', required: true },
{ source: 'industry_code', target: 'Industry', required: false,
transform: (code) => INDUSTRY_MAP[code] || 'Other' },
{ source: 'annual_rev', target: 'AnnualRevenue', required: false,
transform: (v) => typeof v === 'string' ? parseFloat(v.replace(/[$,]/g, '')) : v },
{ source: 'website_url', target: 'Website', required: false },
{ source: 'employee_count', target: 'NumberOfEmployees', required: false },
{ source: 'external_id', target: 'External_ID__c', required: true },
];
function transformRecord(
source: Record<string, any>,
mappings: FieldMapping[]
): Record<string, any> {
const target'Configure Salesforce across Developer, Sandbox, and Production environments.
Salesforce Multi-Environment Setup
Overview
Configure Salesforce integrations across Developer, Sandbox, and Production orgs with environment-specific credentials, login URLs, and deployment promotion flows.
Prerequisites
- Production Salesforce org (Enterprise+ for Full sandbox)
- Salesforce CLI authenticated to all environments
- Secret management solution (Vault, AWS/GCP Secrets Manager)
Instructions
Step 1: Salesforce Environment Types
| Environment | Org Type | Login URL | Purpose | Data |
|---|---|---|---|---|
| Development | Developer Edition or Scratch Org | login.salesforce.com | Local dev | Sample data |
| QA | Developer Sandbox | test.salesforce.com | Testing | Subset of prod |
| Staging | Full Sandbox | test.salesforce.com | Pre-prod validation | Copy of prod |
| Production | Production Org | login.salesforce.com | Live traffic | Real data |
Step 2: Sandbox Types
| Sandbox Type | Data | Metadata | Refresh Interval | Use Case |
|---|---|---|---|---|
| Developer | None | Copy of prod | 1 day | Feature development |
| Developer Pro | None | Copy of prod | 1 day | Integration testing |
| Partial Copy | Sampled | Copy of prod | 5 days | QA with realistic data |
| Full | Full copy | Copy of prod | 29 days | Staging, UAT, load testing |
Step 3: Environment Configuration
// src/config/salesforce.ts
interface SalesforceEnvConfig {
loginUrl: string;
username: string;
apiVersion: string;
isSandbox: boolean;
}
const envConfigs: Record<string, SalesforceEnvConfig> = {
development: {
loginUrl: 'https://login.salesforce.com', // Or test.salesforce.com for sandbox
username: process.env.SF_USERNAME_DEV!,
apiVersion: '59.0',
isSandbox: false, // true if using a sandbox for dev
},
staging: {
loginUrl: 'https://test.salesforce.com', // ALL sandboxes use test.salesforce.com
username: process.env.SF_USERNAME_STAGING!,
apiVersion: '59.0',
isSandbox: true,
},
production: {
loginUrl: 'https://login.salesforce.com',
username: process.env.SF_USERNAME_PROD!,
apiVersion: '59.0',
isSandbox: false,
},
};
export function getSalesforceConfig(): SalesforceEnvConfig {
const env = process.env.NODE_ENV || 'development';
const config = envConfigs[env];
if (!config) throw new Error(`No Salesforce config for environment: ${en'Set up observability for Salesforce integrations with API limit monitoring,.
Salesforce Observability
Overview
Instrument Salesforce integrations with API limit monitoring, SOQL performance tracking, error classification, and alerting. Uses Salesforce's built-in Limits API and EventLogFile for deep visibility.
Prerequisites
- jsforce connection configured
- Prometheus or compatible metrics backend (optional)
- Grafana or similar dashboarding tool (optional)
- Salesforce Enterprise+ for EventLogFile access
Instructions
Step 1: API Limit Monitoring (Core Metric)
import { getConnection } from './salesforce/connection';
import { Registry, Gauge, Counter, Histogram } from 'prom-client';
const registry = new Registry();
// The single most important Salesforce metric
const apiLimitGauge = new Gauge({
name: 'salesforce_api_limit_remaining',
help: 'Remaining daily API calls',
registers: [registry],
});
const apiLimitMaxGauge = new Gauge({
name: 'salesforce_api_limit_max',
help: 'Maximum daily API calls',
registers: [registry],
});
const apiUsagePercent = new Gauge({
name: 'salesforce_api_usage_percent',
help: 'Percentage of daily API calls used',
registers: [registry],
});
// Poll limits every 5 minutes (each poll = 1 API call)
setInterval(async () => {
try {
const conn = await getConnection();
const limits = await conn.request('/services/data/v59.0/limits/');
apiLimitGauge.set(limits.DailyApiRequests.Remaining);
apiLimitMaxGauge.set(limits.DailyApiRequests.Max);
const used = limits.DailyApiRequests.Max - limits.DailyApiRequests.Remaining;
apiUsagePercent.set((used / limits.DailyApiRequests.Max) * 100);
} catch (error) {
console.error('Failed to poll SF limits:', error);
}
}, 5 * 60 * 1000);
Step 2: Request Instrumentation
const sfRequestDuration = new Histogram({
name: 'salesforce_request_duration_seconds',
help: 'Salesforce API request duration',
labelNames: ['operation', 'sobject'],
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [registry],
});
const sfRequestCounter = new Counter({
name: 'salesforce_requests_total',
help: 'Total Salesforce API requests',
labelNames: ['operation', 'sobject', 'status'],
registers: [registry],
});
const sfErrorCounter = new Counter({
name: 'salesforce_errors_total',
help: 'Salesforce errors by error code',
labelNames: ['error_code', 'sobject'],
registers: [registry],
});
// Instrumented wrapper for all SF operations
async function instrumentedSfCall<T>(
operation: string,
sobject: string,
fn: () => Promise<T>
): Promise<T> {
const timer = sfRequestDuration.startTimer({ operation, sobject });
try {
c'Optimize Salesforce API performance with SOQL tuning, Composite API.
Salesforce Performance Tuning
Overview
Optimize Salesforce API performance: tune SOQL queries, minimize API calls using Composite/Collections APIs, implement metadata caching, and handle large result sets efficiently.
Prerequisites
- jsforce connection configured
- Understanding of SOQL query plans
- Redis or in-memory cache available (optional)
- Access to Setup > API usage monitoring
Instructions
Step 1: SOQL Query Optimization
// BAD: SELECT * equivalent — fetches all fields
const result = await conn.query('SELECT FIELDS(ALL) FROM Account LIMIT 100');
// GOOD: Only select fields you need
const result = await conn.query(`
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
LIMIT 100
`);
// BAD: Non-selective WHERE clause (full table scan)
const result = await conn.query("SELECT Id FROM Contact WHERE Title LIKE '%Engineer%'");
// GOOD: Use indexed fields in WHERE (Id, Name, CreatedDate, RecordType, lookup fields)
const result = await conn.query(`
SELECT Id, Name, Title
FROM Contact
WHERE AccountId = '001xxxxxxxxxxxx'
AND CreatedDate >= LAST_N_DAYS:30
LIMIT 200
`);
// Use relationship queries to avoid N+1 pattern
// BAD: Query Accounts, then query Contacts for each (N+1 API calls)
const accounts = await conn.query('SELECT Id FROM Account LIMIT 50');
for (const acct of accounts.records) {
await conn.query(`SELECT Id FROM Contact WHERE AccountId = '${acct.Id}'`);
// 50 extra API calls!
}
// GOOD: Single relationship query (1 API call)
const accountsWithContacts = await conn.query(`
SELECT Id, Name,
(SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 20)
FROM Account
WHERE Industry = 'Technology'
LIMIT 50
`);
Step 2: Reduce API Call Count
// STRATEGY 1: sObject Collections — 200 records per API call
// Instead of 100 individual creates = 100 API calls
const contacts = Array.from({ length: 100 }, (_, i) => ({
FirstName: `User${i}`,
LastName: `Test`,
Email: `user${i}@test.com`,
}));
await conn.sobject('Contact').create(contacts); // 1 API call
// STRATEGY 2: Composite API — 25 mixed operations per API call
// Create Account + Contact + Opportunity = 1 API call instead of 3
// See salesforce-core-workflow-b
// STRATEGY 3: queryMore for pagination — FREE (doesn't count as extra call)
let result = await conn.query('SELECT Id, Name FROM Contact');
let allRecords = [...result.records];
while (!result.done) {
result = await conn.queryMore(result.nextRecordsUrl!);
allRecords.push(...result.records);
}
Step 3: Cache Metadata (Describe Calls)
import { LRUCache } from 'lru-cache';
// Describe calls are expensive and metadata rarely ch'Implement Salesforce lint rules, SOQL injection prevention, and API.
Salesforce Policy & Guardrails
Overview
Automated policy enforcement for Salesforce integrations: SOQL injection prevention, API key leak detection, governor limit guardrails, and CI pipeline checks.
Prerequisites
- ESLint configured in project
- jsforce TypeScript project
- CI/CD pipeline with policy checks
- Understanding of Salesforce security model
Instructions
Step 1: SOQL Injection Prevention
// CRITICAL: Never concatenate user input into SOQL strings
// BAD — SOQL injection vulnerability
async function findAccount(name: string) {
return conn.query(`SELECT Id FROM Account WHERE Name = '${name}'`);
// User input: "'; DELETE FROM Account; --"
// Result: SOQL injection (though Salesforce doesn't support DELETE via SOQL,
// user can still extract data with UNION-like techniques)
}
// GOOD — Escape special characters
function escapeSoql(value: string): string {
return value
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/%/g, '\\%')
.replace(/_/g, '\\_');
}
async function findAccountSafe(name: string) {
const safeName = escapeSoql(name);
return conn.query(`SELECT Id, Name FROM Account WHERE Name = '${safeName}'`);
}
// BEST — Use parameterized queries with jsforce
// jsforce doesn't have native parameterized SOQL, so always use escapeSoql()
// For Apex, use bind variables:
// [SELECT Id FROM Account WHERE Name = :accountName]
Step 2: ESLint Rules for Salesforce
// eslint-plugin-salesforce-integration/rules/no-soql-injection.js
module.exports = {
meta: {
type: 'problem',
docs: { description: 'Prevent SOQL injection by detecting string concatenation in query calls' },
},
create(context) {
return {
CallExpression(node) {
// Detect conn.query(`...${variable}...`)
if (
node.callee.property?.name === 'query' &&
node.arguments[0]?.type === 'TemplateLiteral' &&
node.arguments[0].expressions.length > 0
) {
// Check if expressions use the escapeSoql wrapper
for (const expr of node.arguments[0].expressions) {
if (expr.type !== 'CallExpression' || expr.callee?.name !== 'escapeSoql') {
context.report({
node: expr,
message: 'SOQL injection risk: wrap user input with escapeSoql(). Example: `WHERE Name = \'${escapeSoql(userInput)}\'`',
});
}
}
}
},
};
},
};
Step 3: Credential Leak Detection
#!/bin/bash
# pre-commit-salesforce-check.sh
# Detect Salesforce credential pat'Execute Salesforce production deployment checklist with sandbox testing.
Salesforce Production Checklist
Overview
Complete checklist for deploying Salesforce integrations to production, including sandbox validation, API limit planning, and rollback procedures.
Prerequisites
- Staging/sandbox environment tested and verified
- Production Connected App configured
- Dedicated integration user in production
- Monitoring and alerting ready
Instructions
Pre-Deployment Configuration
- [ ] Production Connected App has minimum OAuth scopes (not
full) - [ ] Dedicated integration user with restricted profile (not admin)
- [ ] SFLOGINURL set to
https://login.salesforce.com(nottest.salesforce.com) - [ ] All credentials stored in vault/secrets manager (not env files)
- [ ] IP restrictions configured on Connected App and user profile
- [ ] JWT certificate uploaded (if using JWT Bearer flow)
API Limit Planning
- [ ] Estimated daily API calls documented
- [ ] API limit headroom > 20% (
GET /services/data/v59.0/limits/) - [ ] Bulk API used for operations > 200 records
- [ ] Composite API used for multi-object transactions
- [ ] sObject Collections used for batch CRUD (max 200/call)
- [ ] Caching implemented for describe/metadata calls
Code Quality
- [ ] All SOQL queries use parameterized filters (no injection)
- [ ] Error handling covers Salesforce error codes (
INVALIDFIELD,REQUESTLIMIT_EXCEEDED, etc.) - [ ] Retry logic implemented for transient errors (
UNABLETOLOCKROW,SERVERUNAVAILABLE) - [ ] No hardcoded Salesforce IDs (use External IDs or SOQL lookups)
- [ ] Connection auto-refreshes expired tokens
- [ ] Logging redacts PII and credentials
Sandbox Validation
# Test in Full sandbox first (mirrors production data)
# 1. Deploy to sandbox
sf project deploy start --target-org my-sandbox
# 2. Run integration tests against sandbox
SF_LOGIN_URL=https://test.salesforce.com npm run test:integration
# 3. Verify API limits are within budget
sf limits api display --target-org my-sandbox --json | jq '.result[] | select(.name == "DailyApiRequests")'
# 4. Check Apex test results
sf apex run test --target-org my-sandbox --result-format human --code-coverage
Health Check Endpoint
async function salesforceHealthCheck(): Promise<{
status: 'healthy' | 'degraded' | 'unhealthy';
details: Record<string, any>;
}> {
const conn = await getConnection();
const start = Date.now();
try {
const [identity, limits] = await Promise.all([
conn.identity(),
conn.request('/services/data/v'Implement Salesforce API limit management, backoff, and quota monitoring.
Salesforce Rate Limits
Overview
Handle Salesforce API limits gracefully. Salesforce uses a 24-hour rolling limit (not per-minute), plus concurrent request limits and Bulk API quotas.
Prerequisites
- jsforce connection configured
- Understanding of your org's edition and license count
- Access to Setup > Company Information
Instructions
Step 1: Understand Salesforce API Limits
| Limit Type | Calculation | Example (Enterprise, 50 users) |
|---|---|---|
| Daily API Requests | Base + (per-user * licenses) | 100,000 + (1,000 * 50) = 150,000 |
| Concurrent API (long-running) | 25 per org | 25 |
| Bulk API 2.0 Ingest Jobs | 15,000/day | 15,000 |
| Bulk API 2.0 Query Jobs | 15,000/day | 15,000 |
| Composite Subrequests | 25 per call | 25 |
| SOQL Query Row Limit | 50,000 per query | 50,000 |
| sObject Collections | 200 records per call | 200 |
Key difference from most SaaS APIs: Salesforce limits are per-org, not per-user or per-key. All integrations sharing the same org share the same pool.
Step 2: Monitor Remaining Quota
import { getConnection } from './salesforce/connection';
async function checkApiLimits(): Promise<{
used: number;
remaining: number;
max: number;
percentUsed: number;
}> {
const conn = await getConnection();
const limits = await conn.request('/services/data/v59.0/limits/');
const daily = limits.DailyApiRequests;
const used = daily.Max - daily.Remaining;
const percentUsed = (used / daily.Max) * 100;
return {
used,
remaining: daily.Remaining,
max: daily.Max,
percentUsed: Math.round(percentUsed * 10) / 10,
};
}
// Also available in every REST API response header:
// Sforce-Limit-Info: api-usage=135/150000
Step 3: Implement Backoff for REQUESTLIMITEXCEEDED
async function withSalesforceRetry<T>(
operation: () => Promise<T>,
config = { maxRetries: 5, baseDelayMs: 2000, maxDelayMs: 60000 }
): Promise<T> {
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const errorCode = error.errorCode || error.name;
// Only retry on transient/limit errors
const retryable = [
'REQUEST_LIMIT_EXCEEDED',
'SERVER_UNAVAILABLE',
'UNABLE_TO_LOCK_ROW',
];
if (attempt === config.maxRetries || !retryable.includes(errorCode)) {
throw error;
}
// Exponential backoff with jitter
const exponentialDelay = confi'Implement Salesforce integration reference architecture with jsforce,.
Salesforce Reference Architecture
Overview
Production-ready architecture patterns for Salesforce integrations, covering Node.js integration apps, SFDX metadata projects, and event-driven sync architectures.
Prerequisites
- Understanding of layered architecture
- jsforce and Salesforce CLI experience
- TypeScript project setup
- Decision on sync model (polling vs event-driven)
Project Structure
Node.js Integration App
my-sf-integration/
├── src/
│ ├── salesforce/
│ │ ├── connection.ts # Singleton jsforce connection with auto-refresh
│ │ ├── types.ts # Typed sObject interfaces (Account, Contact, etc.)
│ │ ├── queries.ts # SOQL query builders
│ │ ├── mutations.ts # Create/update/delete operations
│ │ └── events.ts # CDC and Platform Event subscribers
│ ├── services/
│ │ ├── account-sync.ts # Business logic for Account sync
│ │ ├── contact-sync.ts # Business logic for Contact sync
│ │ └── opportunity-sync.ts # Pipeline/forecast sync
│ ├── api/
│ │ ├── routes.ts # Express/Fastify routes
│ │ └── health.ts # Health check with SF connectivity
│ ├── jobs/
│ │ ├── full-sync.ts # Scheduled full data sync
│ │ └── incremental-sync.ts # CDC-based incremental sync
│ └── index.ts
├── tests/
│ ├── unit/ # Mocked jsforce tests
│ └── integration/ # Live sandbox tests
├── config/
│ ├── default.json # Shared config
│ └── production.json # Production overrides
└── package.json
SFDX Metadata Project (Apex, LWC, Triggers)
my-sf-app/
├── force-app/main/default/
│ ├── classes/ # Apex classes
│ │ ├── AccountTriggerHandler.cls
│ │ ├── ContactService.cls
│ │ └── IntegrationService.cls
│ ├── triggers/ # Apex triggers
│ │ └── AccountTrigger.trigger
│ ├── lwc/ # Lightning Web Components
│ │ └── accountList/
│ ├── objects/ # Custom object metadata
│ │ └── Integration_Log__c/
│ ├── permissionsets/
│ │ └── Integration_API_Access.permissionset-meta.xml
│ └── flows/ # Screen/record-triggered flows
├── scripts/apex/ # Anonymous Apex scripts
├── config/
│ └── project-scratch-def.json
└── sfdx-project.json
Integration Patterns
Pattern A: Polling-Based Sync
┌─────────────┐ SOQL Query ┌─────────────┐
│ Your App │ ──────────────────▶ │ Salesforce │
│ (cron job) │ SELECT ... WHERE │ Org │
│ │ ◀────────────────── │ │
│ │ JSON Records │ │
└─────────────┘ └─────────────┘
Pros: Simple, works with any edition
Cons: Latency (polling interval), wastes API calls on empty polls
Use: Small d'Implement Salesforce reliability patterns including circuit breakers,.
Salesforce Reliability Patterns
Overview
Production-grade reliability patterns for Salesforce integrations: circuit breakers for API outages, idempotent operations using External IDs, graceful degradation with cached data, and dead letter queues for failed operations.
Prerequisites
- jsforce connection configured
- Understanding of Salesforce error codes (see
salesforce-common-errors) - Redis or database for state management (optional)
- opossum or similar circuit breaker library
Instructions
Step 1: Circuit Breaker for Salesforce API
import CircuitBreaker from 'opossum';
import { getConnection } from './salesforce/connection';
// Circuit breaker wraps all Salesforce calls
const sfBreaker = new CircuitBreaker(
async (fn: () => Promise<any>) => fn(),
{
timeout: 30000, // SF calls can be slow — 30s timeout
errorThresholdPercentage: 50, // Open circuit at 50% error rate
resetTimeout: 60000, // Try again after 1 minute
volumeThreshold: 10, // Need 10 calls before evaluating
errorFilter: (error: any) => {
// Don't count client errors as circuit-breaking failures
const nonCircuitErrors = ['INVALID_FIELD', 'MALFORMED_QUERY', 'REQUIRED_FIELD_MISSING'];
return nonCircuitErrors.includes(error.errorCode);
},
}
);
sfBreaker.on('open', () => {
console.error('CIRCUIT OPEN: Salesforce API failing — requests will fail fast');
// Alert ops team
});
sfBreaker.on('halfOpen', () => {
console.info('CIRCUIT HALF-OPEN: Testing Salesforce recovery...');
});
sfBreaker.on('close', () => {
console.info('CIRCUIT CLOSED: Salesforce API recovered');
});
// Usage — all SF calls go through the breaker
async function safeSfQuery<T>(soql: string): Promise<T[]> {
return sfBreaker.fire(async () => {
const conn = await getConnection();
const result = await conn.query<T>(soql);
return result.records;
});
}
Step 2: Idempotent Operations with External IDs
// Salesforce's upsert with External ID is naturally idempotent
// Same data sent twice = same result (no duplicates)
async function idempotentSync(
objectType: string,
records: Record<string, any>[],
externalIdField: string = 'External_ID__c'
): Promise<{ success: number; failed: number; errors: any[] }> {
const conn = await getConnection();
let success = 0;
let failed = 0;
const errors: any[] = [];
// Process in batches of 200 (sObject Collections limit)
for (let i = 0; i < records.length; i += 200) {
const batch = records.slice(i, i + 200);
const results = await conn.sobject(objectType).upsert(batch, externalIdField);
for (const resu'Apply production-ready Salesforce jsforce patterns for TypeScript and.
Salesforce SDK Patterns
Overview
Production-ready patterns for jsforce (Node.js) and simple-salesforce (Python) — singleton connections, typed queries, error handling, and token refresh.
Prerequisites
- Completed
salesforce-install-authsetup - Familiarity with async/await and TypeScript generics
- Understanding of Salesforce sObject model
Instructions
Step 1: Singleton Connection with Auto-Refresh
// src/salesforce/connection.ts
import jsforce from 'jsforce';
let conn: jsforce.Connection | null = null;
export async function getConnection(): Promise<jsforce.Connection> {
if (conn?.accessToken) {
// Test if token is still valid
try {
await conn.identity();
return conn;
} catch {
conn = null; // Token expired, reconnect
}
}
conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL || 'https://login.salesforce.com',
version: '59.0', // Pin API version for stability
});
await conn.login(
process.env.SF_USERNAME!,
process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
);
return conn;
}
Step 2: Typed sObject Interfaces
// src/salesforce/types.ts
/** Standard Salesforce sObject base fields */
interface SObjectBase {
Id: string;
CreatedDate: string;
LastModifiedDate: string;
SystemModstamp: string;
IsDeleted: boolean;
}
export interface Account extends SObjectBase {
Name: string;
Industry?: string;
AnnualRevenue?: number;
NumberOfEmployees?: number;
Website?: string;
Phone?: string;
BillingCity?: string;
BillingState?: string;
OwnerId: string;
}
export interface Contact extends SObjectBase {
FirstName?: string;
LastName: string;
Email?: string;
Phone?: string;
AccountId?: string;
Title?: string;
Department?: string;
}
export interface Opportunity extends SObjectBase {
Name: string;
Amount?: number;
StageName: string;
CloseDate: string;
AccountId?: string;
Probability?: number;
ForecastCategory?: string;
}
export interface Lead extends SObjectBase {
FirstName?: string;
LastName: string;
Company: string;
Email?: string;
Status: string;
IsConverted: boolean;
}
Step 3: Type-Safe Query Builder
// src/salesforce/queries.ts
import { getConnection } from './connection';
import type { Account, Contact, Opportunity } from './types';
export async function queryAccounts(
filters?: { industry?: string; minRevenue?: number }
): Promise<Account[]> {
const conn = await getConnection();
let soql = `
SELECT Id, Name, Industry, AnnualRevenue, NumberOfEmployees,
Website, Phone, BillingCity, BillingState, OwnerId
FROM Account
`;
const conditions: string[] = [];
if (filters?.industry) {
condit'Apply Salesforce security best practices for Connected Apps, OAuth,.
Salesforce Security Basics
Overview
Security best practices for Salesforce integrations: Connected App configuration, OAuth scope management, field-level security, and credential rotation.
Prerequisites
- Salesforce org with System Administrator access
- Connected App created in Setup > App Manager
- Understanding of Salesforce security model (Profile, Permission Set, OWD)
Instructions
Step 1: Secure Connected App Configuration
Setup > App Manager > New Connected App:
1. Enable OAuth Settings
2. Callback URL: https://yourapp.com/oauth/callback (NOT localhost in prod)
3. Selected OAuth Scopes — USE MINIMUM REQUIRED:
- "Manage user data via APIs (api)" — for REST/SOQL access
- "Perform requests at any time (refresh_token, offline_access)" — for refresh tokens
- DO NOT add "Full access (full)" unless absolutely necessary
4. Require Proof Key for Code Exchange (PKCE): Enable for public clients
5. Require Secret for Web Server Flow: Enable
6. IP Relaxation: "Enforce IP restrictions" (not "Relax IP restrictions")
Step 2: Credential Storage
# .env (NEVER commit to git)
SF_LOGIN_URL=https://login.salesforce.com
SF_USERNAME=integration-user@yourcompany.com
SF_PASSWORD=<from-vault>
SF_SECURITY_TOKEN=<from-vault>
SF_CLIENT_ID=<connected-app-consumer-key>
SF_CLIENT_SECRET=<connected-app-consumer-secret>
# .gitignore — ALWAYS include
.env
.env.local
.env.*.local
server.key # JWT private key
*.pem
*.key
Step 3: Use a Dedicated Integration User
Create a dedicated Salesforce user for API access:
1. Profile: Create "API Integration" profile (clone from Standard User)
- Login Hours: restrict to expected operating hours
- Login IP Ranges: restrict to your server IPs
- Object permissions: ONLY objects your integration needs
2. Permission Set: "Integration API Access"
- Object: Account — Read, Create, Edit (no Delete)
- Object: Contact — Read, Create, Edit (no Delete)
- Field-Level Security: only expose fields the integration reads/writes
3. NEVER use a System Administrator user for integrations
Step 4: Field-Level Security (FLS) Enforcement
// Always check FLS before operations — especially for managed packages
const conn = await getConnection();
const meta = await conn.sobject('Account').describe();
// Check if field is accessible (readable)
const industryField = meta.fields.find(f => f.name === 'Industry');
if (!industryField?.accessible) {
throw new Error('Industry field is not accessible — check FLS');
}
// Check if field is updateable (writable)
if (!industryField?.updateable) {
console.warn('Industry field is read-only for this user');
}
// Check wh'Analyze, plan, and execute Salesforce API version upgrades and jsforce.
Salesforce Upgrade & Migration
Overview
Guide for upgrading Salesforce API versions (v55.0 to v59.0+), migrating between jsforce major versions, and handling Salesforce seasonal release changes.
Prerequisites
- Current jsforce or simple-salesforce installed
- Git for version control
- Test suite with Salesforce integration tests
- Sandbox environment for validation
Instructions
Step 1: Check Current Versions
# jsforce version
npm list jsforce
# Current API version in use
node -e "const jsforce = require('jsforce'); const c = new jsforce.Connection({}); console.log('Default API version:', c.version)"
# Available API versions from your org
sf org display --target-org my-org --json | jq '.result.apiVersion'
Step 2: Salesforce API Version Changes
| API Version | Release | Key Changes |
|---|---|---|
| v59.0 | Winter '24 | Composite Graph improvements, Einstein AI endpoints |
| v58.0 | Summer '23 | Enhanced Bulk API 2.0, Flow API updates |
| v57.0 | Spring '23 | SOQL TYPEOF improvements, new standard fields |
| v56.0 | Winter '23 | sObject Collections batch size changes |
| v55.0 | Summer '22 | Retirement of old SOAP API features |
Salesforce retires API versions periodically. Versions older than 3 years are typically deprecated. Check Salesforce Release Notes each season.
Step 3: jsforce Major Version Migration
// jsforce v1.x → v2.x/v3.x migration
// Key breaking changes:
// BEFORE (v1.x): Callback-based
import jsforce from 'jsforce';
const conn = new jsforce.Connection();
conn.login(username, password, (err, userInfo) => {
conn.query('SELECT Id FROM Account', (err, result) => {});
});
// AFTER (v2.x+): Promise-based (still supports callbacks)
import jsforce from 'jsforce';
const conn = new jsforce.Connection();
await conn.login(username, password);
const result = await conn.query('SELECT Id FROM Account');
// BEFORE (v1.x): Bulk API v1
const job = conn.bulk.createJob('Account', 'insert');
const batch = job.createBatch();
// AFTER (v2.x+): Bulk API 2.0
const results = await conn.bulk2.loadAndWaitForResults({
object: 'Account',
operation: 'insert',
input: csvData,
});
Step 4: Update API Version in Code
// Pin API version explicitly (recommended for stability)
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL,
version'Implement Salesforce Platform Events, Change Data Capture (CDC), and.
Salesforce Webhooks & Events
Overview
Salesforce doesn't use traditional webhooks. Instead, it offers Platform Events, Change Data Capture (CDC), and Outbound Messages for real-time data flow. All use the CometD (Bayeux) streaming protocol via jsforce.
Prerequisites
- jsforce installed with connection configured
- Platform Events or CDC enabled in your org
- Understanding of publish/subscribe patterns
- Express.js for Outbound Message endpoints
Event Mechanism Comparison
| Mechanism | Direction | Use Case | Retention |
|---|---|---|---|
| Platform Events | Bi-directional | Custom event bus | 72 hours |
| Change Data Capture (CDC) | Salesforce → External | Record change notifications | 3 days |
| Outbound Messages | Salesforce → External | Workflow-triggered HTTP POST | Until confirmed |
| Streaming API (PushTopics) | Salesforce → External | SOQL-based subscriptions | No replay |
Instructions
Step 1: Subscribe to Change Data Capture (CDC)
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!);
// Subscribe to Account changes
// CDC channel format: /data/AccountChangeEvent
const subscription = conn.streaming.topic('/data/AccountChangeEvent').subscribe((message) => {
const header = message.payload.ChangeEventHeader;
console.log('Change Type:', header.changeType); // CREATE, UPDATE, DELETE, UNDELETE
console.log('Record IDs:', header.recordIds);
console.log('Changed Fields:', header.changedFields);
console.log('User ID:', header.commitUser);
// Access changed field values
if (header.changeType === 'UPDATE') {
console.log('New values:', message.payload);
// Only changed fields are populated in the payload
}
});
// Enable CDC for objects in Setup:
// Setup > Integrations > Change Data Capture > Select Objects
Step 2: Publish and Subscribe to Platform Events
// Define a Platform Event in Salesforce:
// Setup > Platform Events > New Platform Event
// Example: Order_Status__e with fields:
// - Order_Id__c (Text)
// - Status__c (Text)
// - Amount__c (Number)
// Publish a Platform Event via API
await conn.sobject('Order_Status__e').create({
Order_Id__c: 'ORD-12345',
Status__c: 'Shipped',
Amount__c: 499.99,
});
// Subscribe to Platform Events
const eventSub = conn.streaming.topic('/event/Order_Status__e').subscribe((message) => {
console.log(How It Works
Skills trigger automatically when you discuss Salesforce topics:
- "Query Salesforce accounts" triggers
salesforce-core-workflow-a - "Set up Salesforce authentication" triggers
salesforce-install-auth - "Bulk import contacts to Salesforce" triggers
salesforce-core-workflow-b - "Debug Salesforce API limit error" triggers
salesforce-common-errors - "Set up Salesforce Change Data Capture" triggers
salesforce-webhooks-events
Ready to use salesforce-pack?
Related Plugins
supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
/plugin install supabase-pack@claude-code-plugins-plus
vercel-pack
Complete Vercel integration skill pack with 30 skills covering deployments, edge functions, preview environments, performance optimization, and production operations. Flagship+ tier vendor pack.
/plugin install vercel-pack@claude-code-plugins-plus
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
/plugin install clay-pack@claude-code-plugins-plus
cursor-pack
Complete Cursor integration skill pack with 30 skills covering AI code editing, composer workflows, codebase indexing, and productivity features. Flagship+ tier vendor pack.
/plugin install cursor-pack@claude-code-plugins-plus
exa-pack
Complete Exa integration skill pack with 30 skills covering neural search, semantic retrieval, web search API, and AI-powered discovery. Flagship+ tier vendor pack.
/plugin install exa-pack@claude-code-plugins-plus
firecrawl-pack
Complete Firecrawl integration skill pack with 30 skills covering web scraping, crawling, markdown conversion, and LLM-ready data extraction. Flagship+ tier vendor pack.
/plugin install firecrawl-pack@claude-code-plugins-plus