From salesforce-pack
Identifies Salesforce pitfalls like SOQL N+1 queries, governor limit violations, API overuse, and SOQL injection during code reviews, onboarding, and integration audits.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin salesforce-packThis skill is limited to using the following tools:
The 10 most common and costly mistakes when integrating with Salesforce, with real error messages and correct patterns.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
The 10 most common and costly mistakes when integrating with Salesforce, with real error messages and correct patterns.
// 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
// 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
// This integration uses 80,000 API calls/day
// Sales team uses 60,000/day
// Total: 140,000 > 150,000 limit → everyone gets blocked
// 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 });
// 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}'`);
function escapeSoql(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}
await conn.query(`SELECT Id FROM Account WHERE Name = '${escapeSoql(name)}'`);
// Fetches ALL fields — 200+ columns on Account, massive payload
const result = await conn.query('SELECT FIELDS(ALL) FROM Account LIMIT 100');
// Select only what you need — 5-10x faster, much less data transfer
const result = await conn.query('SELECT Id, Name, Industry, AnnualRevenue FROM Account LIMIT 100');
// IDs are different across sandbox and production!
const adminProfileId = '00e5f000001abc'; // Works in sandbox...
const queueId = '00G5f000002def'; // ...breaks in production
await conn.sobject('Case').create({ OwnerId: queueId, ProfileId: adminProfileId });
// Look up by name, not by ID
const queue = await conn.query("SELECT Id FROM Group WHERE Name = 'Support Queue' AND Type = 'Queue'");
const queueId = queue.records[0].Id;
const profile = await conn.query("SELECT Id FROM Profile WHERE Name = 'System Administrator'");
const profileId = profile.records[0].Id;
const results = await conn.sobject('Contact').create(contacts);
console.log('Done!'); // Ignoring that some records may have failed
const results = await conn.sobject('Contact').create(contacts);
const failures = results.filter(r => !r.success);
if (failures.length > 0) {
console.error(`${failures.length}/${results.length} records failed:`);
for (const failure of failures) {
console.error(` ${failure.errors.map(e => `${e.statusCode}: ${e.message}`).join('; ')}`);
}
}
// Sandbox login URL used for production — silently connects to sandbox
const conn = new jsforce.Connection({
loginUrl: 'https://test.salesforce.com', // WRONG for production
});
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL, // 'https://login.salesforce.com' for prod
// OR: 'https://test.salesforce.com' for sandboxes
});
// Always use environment variables — never hardcode login URLs
// Without External ID: create duplicates on every sync run
await conn.sobject('Account').create({ Name: 'Acme Corp', Industry: 'Tech' });
// Run again → duplicate Account created!
// With External ID: upsert is idempotent — safe to retry
await conn.sobject('Account').upsert({
External_ID__c: 'CRM-ACME-001', // Custom External ID field
Name: 'Acme Corp',
Industry: 'Tech',
}, 'External_ID__c');
// Run again → updates existing record, no duplicate
// Record locking is COMMON in Salesforce — this crashes on contention
await conn.sobject('Account').update({ Id: accountId, Name: 'New Name' });
// Error: UNABLE_TO_LOCK_ROW → unhandled, crashes the process
async function updateWithRetry(objectType: string, data: any, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await conn.sobject(objectType).update(data);
} catch (error: any) {
if (error.errorCode === 'UNABLE_TO_LOCK_ROW' && attempt < maxRetries) {
await new Promise(r => setTimeout(r, 1000 * attempt)); // Backoff
continue;
}
throw error;
}
}
}
// Poll every 5 minutes — wastes 288 API calls/day even when nothing changed
cron.schedule('*/5 * * * *', async () => {
const changes = await conn.query(`
SELECT Id, Name FROM Account WHERE LastModifiedDate >= ${fiveMinAgo}
`);
// Usually returns 0 records — wasted API call
});
// CDC — only fires when data actually changes, zero wasted API calls
conn.streaming.topic('/data/AccountChangeEvent').subscribe((event) => {
// Only called when an Account is actually created/updated/deleted
handleAccountChange(event);
});
| Pitfall | Detection | Prevention |
|---|---|---|
| N+1 SOQL queries | High API call count, loop with .query() | Use relationship SOQL |
| API limit exhaustion | REQUEST_LIMIT_EXCEEDED | Monitor /limits/, use Bulk API |
| SOQL injection | String concatenation in .query() | Use escapeSoql() |
| SELECT FIELDS(ALL) | Slow queries, large payloads | Select only needed fields |
| Hardcoded IDs | Different behavior in sandbox vs prod | Query by Name, use External IDs |
| Partial failure ignored | Silent data loss | Check .success on every result |
| Wrong login URL | Connected to wrong org | Use SF_LOGIN_URL env var |
| No External ID | Duplicate records on re-sync | Add External_ID__c, use upsert |
| No retry for LOCK_ROW | Random crashes under load | Retry with exponential backoff |
| Polling over CDC | Wasted API calls | Use Change Data Capture |