Complete governor limits reference, Limits class usage, budgeting techniques, and async offloading strategies
From claude-sfdx-iqnpx claudepluginhub bhanu91221/claude-sfdx-iq --plugin claude-sfdx-iqThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
| Limit | Synchronous | Asynchronous |
|---|---|---|
| SOQL queries | 100 | 200 |
| SOQL rows returned | 50,000 | 50,000 |
| SOSL searches | 20 | 20 |
| DML statements | 150 | 150 |
| DML rows processed | 10,000 | 10,000 |
| CPU time | 10,000 ms | 60,000 ms |
| Heap size | 6 MB | 12 MB |
| Callouts | 100 | 100 |
| Callout timeout | 120 seconds total | 120 seconds total |
| Single callout timeout | 60 seconds | 60 seconds |
| Future calls | 50 | 0 (in future context) |
| Queueable jobs | 50 | 1 (from Queueable) |
| sendEmail invocations | 10 | 10 |
| Email recipients | 5,000/day | 5,000/day |
| Describe calls | 100 | 100 |
| QueryLocator rows (Batch) | N/A | 50,000,000 |
| Limit | Value |
|---|---|
| Active batch jobs | 5 (overflow to Flex Queue, max 100) |
| Scheduled Apex jobs | 100 |
| Data storage | Varies by edition |
| File storage | Varies by edition |
| API requests per 24 hours | Varies by edition |
| Platform Events published per hour | Varies by edition |
The Limits class provides runtime inspection of current consumption versus maximums.
public class LimitsMonitor {
public static void logCurrentUsage() {
System.debug('SOQL Queries: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
System.debug('SOQL Rows: ' + Limits.getQueryRows() + ' / ' + Limits.getLimitQueryRows());
System.debug('DML Statements: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
System.debug('DML Rows: ' + Limits.getDmlRows() + ' / ' + Limits.getLimitDmlRows());
System.debug('CPU Time: ' + Limits.getCpuTime() + ' / ' + Limits.getLimitCpuTime());
System.debug('Heap Size: ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
System.debug('Callouts: ' + Limits.getCallouts() + ' / ' + Limits.getLimitCallouts());
System.debug('Future Calls: ' + Limits.getFutureCalls() + ' / ' + Limits.getLimitFutureCalls());
}
public static Boolean isApproachingSOQLLimit(Decimal threshold) {
Decimal usage = (Decimal) Limits.getQueries() / Limits.getLimitQueries();
return usage >= threshold;
}
public static Boolean hasSufficientDML(Integer needed) {
return (Limits.getLimitDmlStatements() - Limits.getDmlStatements()) >= needed;
}
}
| Method | Returns |
|---|---|
Limits.getQueries() | SOQL queries used |
Limits.getLimitQueries() | SOQL query limit |
Limits.getQueryRows() | SOQL rows returned |
Limits.getLimitQueryRows() | SOQL row limit |
Limits.getDmlStatements() | DML statements used |
Limits.getLimitDmlStatements() | DML statement limit |
Limits.getDmlRows() | DML rows processed |
Limits.getLimitDmlRows() | DML row limit |
Limits.getCpuTime() | CPU time in ms |
Limits.getLimitCpuTime() | CPU time limit |
Limits.getHeapSize() | Heap bytes used |
Limits.getLimitHeapSize() | Heap byte limit |
Limits.getCallouts() | Callouts made |
Limits.getLimitCallouts() | Callout limit |
Limits.getFutureCalls() | Future calls made |
Limits.getLimitFutureCalls() | Future call limit |
Before executing an operation, check if sufficient limits remain. This is critical in triggers that share a transaction with other automations.
public class LimitsBudget {
public static void assertSOQLBudget(Integer needed) {
Integer remaining = Limits.getLimitQueries() - Limits.getQueries();
if (remaining < needed) {
throw new LimitsBudgetException(
'Insufficient SOQL budget. Need: ' + needed + ', Remaining: ' + remaining
);
}
}
public static void assertDMLBudget(Integer needed) {
Integer remaining = Limits.getLimitDmlStatements() - Limits.getDmlStatements();
if (remaining < needed) {
throw new LimitsBudgetException(
'Insufficient DML budget. Need: ' + needed + ', Remaining: ' + remaining
);
}
}
public static Boolean canMakeCallout() {
return Limits.getCallouts() < Limits.getLimitCallouts();
}
public class LimitsBudgetException extends Exception { }
}
Usage in a trigger handler:
protected override void afterInsert(List<SObject> newRecords, Map<Id, SObject> newMap) {
// Budget: 1 SOQL for related query, 1 DML for insert
if (LimitsBudget.canExecute(1, 1)) {
createRelatedRecords((List<Account>) newRecords);
} else {
// Offload to async
System.enqueueJob(new CreateRelatedRecordsQueueable(newMap.keySet()));
}
}
// BAD -- N queries for N records
for (Account acc : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
// GOOD -- 1 query for all records
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();
for (Contact con : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
if (!contactsByAccountId.containsKey(con.AccountId)) {
contactsByAccountId.put(con.AccountId, new List<Contact>());
}
contactsByAccountId.get(con.AccountId).add(con);
}
// BAD -- N DML statements
for (Account acc : accounts) {
acc.Status__c = 'Active';
update acc;
}
// GOOD -- 1 DML statement
for (Account acc : accounts) {
acc.Status__c = 'Active';
}
update accounts;
// BAD -- could return 50,000+ rows and hit limit
List<Account> allAccounts = [SELECT Id FROM Account];
// GOOD -- filter and limit
List<Account> activeAccounts = [
SELECT Id FROM Account
WHERE Status__c = 'Active'
AND CreatedDate = LAST_N_DAYS:30
LIMIT 10000
];
// BAD -- O(n*m) nested loops
for (Account acc : accounts) {
for (Contact con : allContacts) {
if (con.AccountId == acc.Id) {
// process
}
}
}
// GOOD -- O(n+m) with Map
Map<Id, List<Contact>> contactMap = groupContactsByAccountId(allContacts);
for (Account acc : accounts) {
List<Contact> related = contactMap.get(acc.Id);
if (related != null) {
// process
}
}
// BAD -- loading entire result into memory
List<Account> allAccounts = [SELECT Id, Name, Description FROM Account];
// GOOD -- use Database.QueryLocator in batch
// Or process in chunks:
for (List<Account> chunk : [SELECT Id, Name FROM Account LIMIT 50000]) {
processChunk(chunk);
}
When a synchronous transaction cannot complete within limits, offload to async.
public class OrderProcessor {
public static void processOrders(List<Order__c> orders) {
if (orders.size() > 50 || Limits.getQueries() > 70) {
// Too many records or too little SOQL budget -- go async
System.enqueueJob(new OrderProcessorQueueable(orders));
} else {
processOrdersSync(orders);
}
}
}
When a trigger needs to perform work that would exceed limits, publish a Platform Event and let a subscriber handle it asynchronously.
// In the trigger handler
protected override void afterInsert(List<SObject> newRecords, Map<Id, SObject> newMap) {
List<Order_Processing_Event__e> events = new List<Order_Processing_Event__e>();
for (SObject record : newRecords) {
events.add(new Order_Processing_Event__e(
Record_Id__c = record.Id,
Action__c = 'PROCESS_NEW'
));
}
EventBus.publish(events);
}
// Subscriber trigger (runs in its own transaction with fresh limits)
trigger OrderProcessingEventTrigger on Order_Processing_Event__e (after insert) {
new OrderProcessingEventHandler().run(Trigger.new);
}
// When processing exceeds 10,000 DML rows
if (recordCount > 10000) {
Database.executeBatch(new LargeVolumeProcessorBatch(criteria), 200);
} else {
processInline(records);
}
Create a Limit_Usage_Log__c custom object to track limit consumption in critical transactions.
public class LimitsLogger {
public static void logUsage(String context) {
Limit_Usage_Log__c log = new Limit_Usage_Log__c(
Context__c = context,
SOQL_Queries__c = Limits.getQueries(),
SOQL_Rows__c = Limits.getQueryRows(),
DML_Statements__c = Limits.getDmlStatements(),
DML_Rows__c = Limits.getDmlRows(),
CPU_Time__c = Limits.getCpuTime(),
Heap_Size__c = Limits.getHeapSize(),
Timestamp__c = DateTime.now()
);
// Use Platform Event to avoid consuming a DML statement
Database.insert(log, false);
}
}
Use the LIMIT_USAGE_FOR_NS debug log line to analyze production limit usage:
Number of SOQL queries: 15 out of 100
Number of query rows: 342 out of 50000
Number of DML statements: 4 out of 150
Number of DML rows: 23 out of 10000
Maximum CPU time: 1243 out of 10000
Maximum heap size: 234521 out of 6000000