Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Sf Governor Limits

ASecurity

Apex per-transaction governor limits and how to stay inside them - the full synchronous and asynchronous limit table for API 67.0 (100/200 SOQL, 150 DML, 50k query rows, 6/12 MB heap, 10s/60s CPU), the Limits and OrgLimits classes, bulkification of DML and SOQL, heap control with SOQL for loops and Database.QueryLocator, CPU-time reduction, Test.startTest/stopTest limit isolation, certified managed package and cross-namespace limits, platform limits for async executions and concurrent long-ru...

2 stars
0 votes
0 copies
0 views
Added 9/19/2026
developmentgobashnodetestingdebuggingapidatabase

Works with

api

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add grzmol/vibe-force --skill sf-governor-limits --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sf Governor Limits?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Sf Governor Limits
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/grzmol-sf-governor-limits/badge)](https://www.skillsdirectory.com/skills/grzmol-sf-governor-limits)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: sf-governor-limits
description: Apex per-transaction governor limits and how to stay inside them - the full synchronous and asynchronous limit table for API 67.0 (100/200 SOQL, 150 DML, 50k query rows, 6/12 MB heap, 10s/60s CPU), the Limits and OrgLimits classes, bulkification of DML and SOQL, heap control with SOQL for loops and Database.QueryLocator, CPU-time reduction, Test.startTest/stopTest limit isolation, certified managed package and cross-namespace limits, platform limits for async executions and concurrent long-running transactions, and a symptom-to-fix remediation playbook. Use when writing triggers, batch or queueable Apex, bulk data processing, or when a run fails with a LimitException, a CPU time error, or a heap error, and when running vf-check apex or vf-check analyzer.
---

# Apex Governor Limits

## When to use

- Writing or reviewing any trigger, trigger handler, batch, queueable, future or scheduled class.
- A run failed with `System.LimitException`, `Apex CPU time limit exceeded`, or `Apex heap size too large`.
- Sizing a bulk job: deciding batch scope, chunking a load, or choosing sync versus async.
- Wave 1 and wave 2 of the vibe-force workflow: `sf-apex-engineer` sizes the work against these
  numbers, `sf-quality-gate` reads the Code Analyzer findings that map to them.

All numbers below come from the Apex Developer Guide for Summer '26 / API version 67.0, the
version pinned in `config/vibe-force.defaults.json`. Limits are per transaction unless the table
says otherwise.

## The numbers you must know by heart

| Limit | Synchronous | Asynchronous |
| --- | --- | --- |
| SOQL queries issued | 100 | 200 |
| Records retrieved by SOQL | 50,000 | 50,000 |
| Records retrieved by `Database.getQueryLocator` | 10,000 | 10,000 |
| SOSL queries issued | 20 | 20 |
| Records per SOSL query | 2,000 | 2,000 |
| DML statements issued | 150 | 150 |
| Records processed by DML, `Approval.process`, `Database.emptyRecycleBin` | 10,000 | 10,000 |
| Stack depth for recursive trigger-firing DML | 16 | 16 |
| Callouts per transaction | 100 | 100 |
| Cumulative callout timeout | 120 s | 120 s |
| `@future` methods per invocation | 50 | 0 in batch and future, 50 in queueable |
| Jobs added with `System.enqueueJob` | 50 | 1 |
| `sendEmail` calls | 10 | 10 |
| Heap size | 6 MB | 12 MB |
| CPU time on Salesforce servers | 10,000 ms | 60,000 ms |
| Transaction execution time | 10 min | 10 min |
| `EventBus.publish` for publish-immediately events | 150 | 150 |

Scheduled Apex is asynchronous but **synchronous limits apply to it**. For Bulk API and Bulk
API 2.0 transactions the effective limit is the higher of the two columns.

The full tables - certified managed package and cross-namespace limits, platform limits, static
limits, size-specific limits, miscellaneous limits, email limits - are in
[references/limit-tables.md](references/limit-tables.md).

## What counts, and what does not

| Operation | Counts against |
| --- | --- |
| `Database.query`, `Database.queryWithBinds`, `Database.countQuery`, `Database.countQueryWithBinds`, `Database.getQueryLocator`, `Database.getQueryLocatorWithBinds` | SOQL query count |
| A parent-child subquery inside a SOQL query | one extra query; subqueries get 3x the top-level number, reported by `Limits.getLimitAggregateQueries()` |
| SOQL against a custom metadata type | nothing - custom metadata records have unlimited SOQL per transaction |
| `insert`, `update`, `upsert`, `delete`, `undelete`, `merge` and their `Database.*` forms, `Approval.process`, `Database.convertLead`, `Database.emptyRecycleBin`, `Database.rollback`, `Database.setSavePoint`, `System.runAs`, `EventBus.publish` for publish-after-commit events | DML statement count |
| Time in the database for DML, SOQL and SOSL | **not** CPU time |
| Waiting for a callout | **not** CPU time |
| Application-server CPU spent inside DML | CPU time |
| HTTP request and response bodies | heap size |

That "not CPU time" row is the one people get wrong. A slow query does not burn CPU time; a loop
over its results does.

## Core patterns

### 1. One DML per collection, never one per record

```apex
public with sharing class LineItemUpdater {
    public static void markHighVolume(List<Line_Item__c> items) {
        List<Line_Item__c> toUpdate = new List<Line_Item__c>();
        for (Line_Item__c li : items) {
            if (li.Units_Sold__c > 10) {
                li.Description__c = 'New description';
                toUpdate.add(li);
            }
        }
        if (!toUpdate.isEmpty()) {
            update as user toUpdate;
        }
    }
}
```

The `if (!toUpdate.isEmpty())` guard is not decoration. An empty-list DML still consumes one of
the 150 statements.

### 2. Query once, key by Id, look up in the loop

```apex
public with sharing class InvoiceRollup {
    public static void apply(List<Invoice_Statement__c> invoices) {
        Map<Id, Invoice_Statement__c> byId = new Map<Id, Invoice_Statement__c>(invoices);

        Map<Id, Decimal> totals = new Map<Id, Decimal>();
        for (AggregateResult ar : [
            SELECT Invoice_Statement__c inv, SUM(Value__c) total
            FROM Line_Item__c
            WHERE Invoice_Statement__c IN :byId.keySet()
            WITH USER_MODE
            GROUP BY Invoice_Statement__c
        ]) {
            totals.put((Id) ar.get('inv'), (Decimal) ar.get('total'));
        }

        for (Invoice_Statement__c inv : invoices) {
            inv.Total__c = totals.containsKey(inv.Id) ? totals.get(inv.Id) : 0;
        }
    }
}
```

One query, one map, no query inside the loop. `GROUP BY` moves the arithmetic into the database,
where it costs no CPU time and no heap.

### 3. SOQL for loop when the result set is large

```apex
public with sharing class ContactSweeper {
    public static void touchAll(Id accountId) {
        List<Contact> batch = new List<Contact>();
        for (List<Contact> chunk : [
            SELECT Id, Description
            FROM Contact
            WHERE AccountId = :accountId
            WITH USER_MODE
        ]) {
            for (Contact c : chunk) {
                c.Description = 'swept';
                batch.add(c);
            }
            update as user batch;
            batch.clear();
        }
    }
}
```

The list form of the SOQL for loop hands you 200 records at a time and lets the previous chunk be
garbage collected. That is the difference between 6 MB of heap and a `LimitException`. The
single-sObject form (`for (Contact c : [SELECT ...])`) also chunks internally at 200.

### 4. Batch Apex when 50,000 rows is not enough

```apex
public with sharing class ExpireQuotesBatch implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator([
            SELECT Id, Status__c FROM Quote__c WHERE Expires_On__c < TODAY AND Status__c != 'Expired'
        ]);
    }

    public void execute(Database.BatchableContext ctx, List<Quote__c> scope) {
        for (Quote__c q : scope) {
            q.Status__c = 'Expired';
        }
        update as system scope;
    }

    public void finish(Database.BatchableContext ctx) {}
}
```

`Database.QueryLocator` in `start` returns up to 50 million records, and **the per-transaction
limits reset for every `execute` call**. That reset is the whole point of Batch Apex. Note that
`Database.getQueryLocator` called as an ordinary method inside a transaction is capped at 10,000
records instead - the 50 million figure applies to the `start` method of a batch job.

Pick the scope size deliberately: `Database.executeBatch(new ExpireQuotesBatch(), 100)` when each
record is expensive, the default 200 otherwise. Smaller scope means more executions, and
executions count against the daily async limit.

### 5. Measure, do not guess

```apex
System.debug(LoggingLevel.INFO, 'SOQL ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
System.debug(LoggingLevel.INFO, 'DML ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
System.debug(LoggingLevel.INFO, 'Rows ' + Limits.getQueryRows() + '/' + Limits.getLimitQueryRows());
System.debug(LoggingLevel.INFO, 'Heap ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
System.debug(LoggingLevel.INFO, 'CPU ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
```

Every consumption method `getX()` has a ceiling twin `getLimitX()`. The full list is in
[references/limits-class-api.md](references/limits-class-api.md).

### 6. Assert the budget in a test

```apex
@IsTest
private class InvoiceRollupTest {
    @IsTest
    static void rollupIsBulkSafe() {
        List<Invoice_Statement__c> invoices = TestDataFactory.invoices(200);
        insert invoices;

        Test.startTest();
        Integer queriesBefore = Limits.getQueries();
        InvoiceRollup.apply(invoices);
        Integer used = Limits.getQueries() - queriesBefore;
        Test.stopTest();

        Assert.isTrue(used <= 2, 'Rollup must stay at 2 queries for any volume, used ' + used);
    }
}
```

`Test.startTest()` resets the governor limits once per test method and gives the code under test a
fresh budget; `Test.stopTest()` restores the test context and forces queued async work to run.
Limits apply individually to each test method. A test that asserts a *constant* query count for a
200-record input is the only reliable proof that code is bulk safe - coverage percentage proves
nothing about limits.

### 7. Check org-level capacity before starting a big job

```apex
Map<String, System.OrgLimit> limits = OrgLimits.getMap();
System.OrgLimit async = limits.get('DailyAsyncApexExecutions');
System.debug(async.getValue() + ' of ' + async.getLimit() + ' async executions used today');
```

Batch Apex pre-checks the required async capacity when `Database.executeBatch` is called. If the
job needs more executions than remain in the rolling 24-hour window, it throws before it starts
and the remaining allowance is left unchanged.

## Anti-patterns

### DML inside a loop

```apex
// wrong - 151st record throws
for (Line_Item__c li : liList) {
    li.Description__c = 'New description';
    update li;
}
```

Fix: collect into a list, one `update` after the loop (pattern 1).

### SOQL inside a loop

```apex
// wrong - one query per iteration
for (Invoice_Statement__c inv : Trigger.new) {
    List<Line_Item__c> items = [SELECT Id FROM Line_Item__c WHERE Invoice_Statement__c = :inv.Id];
}
```

Fix: one query with `IN :ids`, results keyed into a `Map` (pattern 2). Code Analyzer reports this
as `DbInLoop` (Flow) and as the SOQL-in-loop Apex rules; `vf-check analyzer` fails on it.

### Holding every record in one list

```apex
// wrong - 50,000 fat records will not fit in 6 MB
List<Contact> all = [SELECT Id, Description, Big_Text__c FROM Contact];
for (Contact c : all) { ... }
```

Fix: a SOQL for loop (pattern 3), fewer fields in the `SELECT`, or Batch Apex (pattern 4).
Querying only the fields you use is the cheapest heap fix there is.

### Nested loops over two collections

```apex
// wrong - 200 x 200 = 40,000 iterations of body, CPU time dies
for (Account a : accounts) {
    for (Contact c : contacts) {
        if (c.AccountId == a.Id) { ... }
    }
}
```

Fix: build a `Map<Id, List<Contact>>` once, then a single loop. CPU time is the limit that
bulkification alone does not fix - algorithmic shape does.

### Calling `Limits.getHeapSize()` inside a loop

Code Analyzer flags this as its own rule. The call itself is cheap but doing it per iteration is
noise; sample it once before and once after the loop.

### Recursive trigger without a guard

Stack depth for recursive trigger-firing DML is 16, and a trigger that updates its own object can
re-enter. Use a static `Set<Id>` of processed ids, or the bypass mechanism in skill
`sf-fflib-operations`.

### Assuming a certified managed package shares your budget

Certified managed packages get their own copy of most per-transaction limits. Non-certified
packages do **not** - their usage counts against your org's limits. When a limit blows up in a
transaction that touches managed code, find out which kind of package it is before you refactor
your own code.

## Verification

```bash
# Static detection of loop-bound SOQL/DML before anything runs
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" analyzer --changed

# Run the Apex tests, including the bulk-safety assertions
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex --target-org <alias>

# Limit consumption of a single operation, from the log
sf apex run --file scripts/apex/probe.apex --target-org <alias>
sf apex get log --number 1 --target-org <alias> | grep -E "LIMIT_USAGE_FOR_NS|CUMULATIVE_LIMIT_USAGE" -A 20

# Current org-level capacity
sf org list limits --target-org <alias>
```

The `LIMIT_USAGE_FOR_NS` block at the end of a debug log is the authoritative per-namespace
consumption report for a transaction. Set the `APEX_CODE` category to `FINEST` and the `SYSTEM`
category to at least `FINE` to get it - see skill `sf-debugging-logs`.

## References

- [references/limit-tables.md](references/limit-tables.md) - every official limit table for API 67.0
- [references/limits-class-api.md](references/limits-class-api.md) - `Limits` and `OrgLimits` methods
- [references/remediation-playbook.md](references/remediation-playbook.md) - symptom to fix, per limit

Sibling skills: `sf-soql-sosl-optimization` (selectivity, indexes, large data volumes),
`sf-async-apex-patterns` (batch, queueable, future, platform events), `sf-apex-development`
(trigger shape and order of execution), `sf-apex-testing` (test mechanics and coverage gates),
`sf-code-analyzer-quality` (the rules that detect these statically), `sf-debugging-logs`
(reading `LIMIT_USAGE_FOR_NS`), `sf-data-management` (loading volume without a transaction at all).

Source: Apex Developer Guide, "Execution Governors and Limits", Summer '26 / API 67.0
(`atlas.en-us.262.0.apexcode.meta`); Apex Reference Guide, `Limits` and `OrgLimits` classes.

Attribution

grzmolgrzmol
View sourceMore from grzmol →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →