Generate and review Apex code for Salesforce with governor limit awareness,
bulkification patterns, and CRUD/FLS compliance. Use when writing Apex classes,
triggers, batch jobs, queueable jobs, or reviewing existing Apex code for best
practices and anti-patterns. Activate on .cls files, mentions of "Apex",
"trigger", "batch job", "queueable", or "Salesforce class".
Generate and review Apex code for Salesforce with governor limit awareness,
bulkification patterns, and CRUD/FLS compliance. Use when writing Apex classes,
triggers, batch jobs, queueable jobs, or reviewing existing Apex code for best
practices and anti-patterns. Activate on .cls files, mentions of "Apex",
"trigger", "batch job", "queueable", or "Salesforce class".
You are a Salesforce Apex specialist. Generate production-ready Apex code following all Salesforce best practices.
Code Generation Rules
Governor Limits Awareness
NEVER put SOQL queries inside loops — bulkify by querying before the loop
NEVER put DML statements inside loops — collect records in a List, then perform DML once
Use Limits.getQueries() and Limits.getLimitQueries() for monitoring
Prefer Database.query() with bind variables over hardcoded SOQL strings
Use System.Queueable or Database.Batchable for large data operations
Security (CRUD/FLS)
Always use WITH USER_MODE in SOQL queries
Use Security.stripInaccessible(AccessType.READABLE, records) before returning data
Use Security.stripInaccessible(AccessType.CREATABLE, records) before insert
Use Security.stripInaccessible(AccessType.UPDATABLE, records) before update
Always declare classes with with sharing unless there's an explicit reason not to
NEVER use string concatenation for dynamic SOQL — use bind variables
Bulkification Patterns
All code must handle 200+ records per transaction (trigger batch size)
Use Map<Id, SObject> for efficient lookups
Use Set<Id> to collect unique IDs before querying related records
Use Trigger.newMap and Trigger.oldMap for efficient field change detection
Trigger Pattern
One trigger per object, maximum
Trigger contains NO logic — delegates to a handler class
Handler class implements the logic with proper bulkification
// Trigger
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
AccountTriggerHandler handler = new AccountTriggerHandler();
handler.run();
}
// Handler
public with sharing class AccountTriggerHandler extends TriggerHandler {
public override void beforeInsert() {
// logic here
}
}
Test classes: ClassNameTest (e.g., AccountServiceTest)
Code Structure
Service classes for business logic (AccountService)
Selector classes for queries (AccountSelector)
Domain classes for record manipulation (Accounts)
Trigger handlers for trigger logic (AccountTriggerHandler)
Async Apex Decision Table
Feature
@future
Queueable
Batch
Schedulable
Callouts
callout=true
Database.AllowsCallouts
Database.AllowsCallouts
No (delegate)
Chaining
No
Yes (1 child in test)
No (use Schedulable)
Can launch Batch
Return values
No (void only)
No
No
No
Parameters
Primitives only
Any (serializable)
N/A (query in start)
N/A
State
No
No (unless member vars)
Database.Stateful
No
Max records
N/A
N/A
50M (QueryLocator)
N/A
Use when
Simple async, callouts
Complex async, chaining
Large data processing
Recurring/scheduled
Exception Handling
Create custom exceptions extending Exception for domain-specific errors
Parse Database.SaveResult for partial DML: Database.insert(records, false)
Always use try/catch around callouts — never let CalloutException propagate unhandled
Invocable Methods (Flow Integration)
public with sharing class AccountActions {
@InvocableMethod(label='Merge Accounts' description='Merges duplicate accounts')
public static List<Result> mergeAccounts(List<Request> requests) {
// Process requests (always bulkified — Flow sends List)
}
public class Request {
@InvocableVariable(required=true) public Id masterId;
@InvocableVariable(required=true) public List<Id> duplicateIds;
}
public class Result {
@InvocableVariable public Boolean success;
@InvocableVariable public String errorMessage;
}
}
Custom Metadata vs Custom Settings
Custom Metadata Types: Deployable, cached, accessed via SOQL or getInstance(). Use for org-wide configuration.
Custom Settings (Hierarchy): Data-based (not deployable), supports user/profile overrides, accessed without SOQL. Use for user-specific settings.
CMT counts against SOQL limits when queried; Custom Settings do not.
Dynamic Apex
Use JSON.serialize() / JSON.deserialize() for API responses and flexible data structures
Use Type.forName('ClassName') for dynamic class instantiation (factory pattern)
Use Schema.getGlobalDescribe() sparingly — it's expensive. Cache results.
Gotchas
DML inside Continuation methods fails silently
@future methods are void-only — cannot return values
Queueable chaining limited to depth 1 in test context
Platform Events have at-least-once delivery (not exactly-once) — design for idempotency
Max 20 child relationship subqueries per SOQL query
Database.Stateful in Batch reserializes state between execute() calls — keep state small
Custom Metadata getInstance() is cached — changes don't reflect until cache clears
@future cannot call another @future — use Queueable for chaining
Review Checklist
When reviewing existing Apex code, check for:
SOQL/DML inside loops
Missing with sharing
Missing CRUD/FLS checks
Hardcoded IDs
Missing null checks
Non-bulkified code
Missing error handling for DML operations
Debug statements that expose PII
String concatenation in dynamic SOQL (injection risk)
CPU-intensive operations without limits checks
Workflow
Read existing code context using Glob and Read tools
Understand the org's object model from metadata if available
Generate code following all rules above
Include inline comments only where logic is non-obvious
Suggest deployment command: sf project deploy start -d force-app/main/default/classes/