Generating Apex
Use this skill for production-grade Apex: new classes, selectors, services, async jobs,
invocable methods, and triggers; and for evidence-based review of existing
OR
.
Required Inputs
Gather or infer before authoring:
- Class type (service, selector, domain, batch, queueable, schedulable, invocable, trigger, trigger action, DTO, utility, interface, abstract, exception, REST resource)
- Target object(s) and business goal
- Class name (derive using the naming table below)
- Net-new vs refactor/fix; any org/API constraints
- Deployment targets (default to runSpecifiedTests and use generated tests where applicable)
Defaults unless specified:
- Sharing: (see sharing rules per type below)
- Access: (use only when required by managed packages or )
- API version: (minimum version)
- ApexDoc comments: yes
If the user provides a clear, complete request, generate immediately without unnecessary back-and-forth.
Workflow
All steps are sequential. Do not skip, merge, or reorder. If blocked, stop and ask for missing context. If not applicable, mark
with a one-line justification in the report.
Phase 1 — Author
-
Discover project conventions
- Service-Selector-Domain layering, logging utilities
- Existing classes/triggers and current trigger framework or handler pattern
- Whether Trigger Actions Framework (TAF) is already in use
-
Choose the smallest correct pattern (see Type-Specific Guidance below)
-
Review templates and assets
- Read the matching template from before authoring (see Type-Specific Guidance for the file mapping)
- When a example exists for the type, read it as a concrete style guide
- For any test class work, always read and use skill
-
Author with guardrails -- apply every rule in the Rules section below
- Generate with ApexDoc
- Generate
-
Generate test classes -- Load the skill
to create
and
{ClassName}Test.cls-meta.xml
. Apex tests are always required to be generated to deploy. No test file creation or edits can occur without loading the
skill to generate tests.
Phase 2 — Validate (required before reporting)
Writing files is the midpoint, not the finish line. Steps 6 and 7 each require a tool invocation and produce output that must appear in the Step 8 report. Do not summarize or present the report until both steps have run and their output is captured.
-
Run code analyzer
- Invoke MCP on all generated/updated files.
- Remediate all , , and violations; re-run until clean.
- Capture the final tool output verbatim for the report.
- Fallback:
sf code-analyzer run --target <target>
. If both are unavailable, record run_code_analyzer=unavailable: <error>
in the report.
-
Execute Apex tests
- Run org tests including via or MCP.
- Delegate all test generation/fixes/coverage work to ; iterate until the tests pass.
- Capture pass/fail counts and coverage percentage for the report.
- If unavailable, record
test_execution=unavailable: <error>
in the report.
Phase 3 — Report
- Report -- use the output format at the bottom of this file.
- The line must contain the actual Step 6 tool output (or
run_code_analyzer=unavailable: <reason>
after attempting invocation).
- The line must contain the actual Step 7 results (or
test_execution=unavailable: <reason>
after attempting invocation).
- A report missing either line is incomplete. Always attempt the tool invocation before recording unavailable.
Rules
Hard-Stop Constraints (Must Enforce)
If any constraint would be violated in generated code, stop and explain the problem before proceeding:
| Constraint | Rationale |
|---|
| Place all SOQL outside loops | Avoid query governor limits (100 queries) |
| Place all DML outside loops | Avoid DML governor limits (150 statements) |
| Declare a sharing keyword on every class | Prevent unintended defaults and data exposure |
| Use Custom Metadata/Labels/describe calls instead of hardcoded IDs | Ensure portability across orgs |
| Always handle exceptions (log, rethrow, or recover) | Prevent silent failures |
| Use bind variables for all dynamic SOQL with user input | Prevent SOQL injection |
| Use Apex-native collections (, , ) rather than Java types | Prevent compile errors |
| Verify methods exist in Apex before use | Prevent reliance on non-existent APIs |
| Avoid in main code paths | Debug statements evaluate even when loggign is not active and consume CPU. Use a logging framework if required on main code paths |
| Never use methods | Use Queueable with ; cannot chain, cannot be called from Batch, and cannot accept non-primitive types |
Bulkification & Governor Limits
- All public APIs accept and process collections; single-record overloads delegate to the bulk method
- In batch/bulk flows, prefer partial-success DML (
Database.update(records, false)
) and process for errors
- Use constructor for efficient ID-based lookups from query results
- Use to group child records by parent; build the map in a single loop before processing
- Use for deduplication and membership checks; prefer over
- Use relationship subqueries to fetch parent + child records in a single SOQL when both are needed
- Use with for rollup calculations instead of querying and counting in Apex
- Only DML records that actually changed — compare against or prior state before adding to the update list
- Use ,
Limits.getDmlStatements()
, to monitor consumption in complex transactions
SOQL Optimization
- Use selective queries with proper clauses; use indexed fields (, , , lookup/master-detail fields, fields, custom indexes) in filters when possible
- does not exist in SOQL -- always specify the exact fields needed
- Apply clauses to bound result sets; use for deterministic results
- When querying Custom Metadata Types (objects ending with ), do NOT use SOQL — use the built-in methods (
{CustomMdt__mdt}.getAll().values()
, , etc.)
Caching
- Use Platform Cache ( / ) for frequently accessed, rarely changed data; set a TTL and always handle cache misses — cache can be evicted at any time
- Use fields as transaction-scoped caches to prevent duplicate queries within the same execution context; lazy-initialize on first access
Security
- Default to ; document justification for or
- in SOQL and for DML for CRUD/FLS enforcement
- Validate dynamic field/operator names via allowlist or
- Named Credentials for all external credentials/API keys
- for user-facing errors (no internal details)
- requires a Custom Permission check
- Isolate logic in dedicated helper classes; call from entry points to limit elevated-access scope
- Encrypt PII/sensitive data at rest via Platform Encryption; never expose PII in debug statements, error messages, or API responses
Security Verification
Before finalizing, verify: CRUD/FLS enforced (SOQL + DML) · explicit sharing keyword on every class · no hardcoded secrets or Record IDs · PII excluded from logs and error messages · error messages sanitized for end users.
Error Handling
- Catch specific exceptions before generic ; include context in messages
- Use only around code that can throw (DML, callouts, JSON parsing, casts); avoid defensive wrapping of simple assignments/collection ops/arithmetic
- Preserve exception cause chains:
new CustomException('message', cause)
(do not replace stack trace with concatenated messages)
- Provide a custom exception class per service domain when meaningful
- In methods, catch exceptions and rethrow as
- Fallback option: when no meaningful domain exception exists, catch generic and either rethrow it or wrap it in a minimal custom exception that preserves the original cause.
Null Safety
- Add guard clauses for null/empty inputs at the top of every public method; match style to context: early in private/trigger-handler methods, exceptions in public APIs, in validation services
- Return empty collections instead of
- Use safe navigation () for chained property access
- Never dereference inline unless presence is guaranteed; use , assignment+null check, or safe navigation first
- Use null coalescing () for default values
- Prefer over manual checks like
value == null || value.trim().isEmpty()
Constants & Literals
- Use enums over string constants whenever possible; enum values follow
- Extract repeated literal strings/numbers into constants or a constants class
- Use custom labels for user-facing strings
- Use Custom Metadata for configurable values (thresholds, mappings, feature flags)
- Never output HTML-escaped entities in code (e.g., ); use literal single quotes in Apex string literals
Naming Conventions
| Type | Pattern | Example |
|---|
| Service | | |
| Selector | | |
| Domain | | |
| Batch | | AccountDeduplicationBatch
|
| Queueable | | |
| Schedulable | | |
| DTO | | |
| Wrapper | | |
| Utility | | |
| Interface | | |
| Abstract | | AbstractIntegrationService
|
| Exception | | |
| REST Resource | | |
| Trigger | | |
| Trigger Action | | |
Additional naming rules:
- Classes:
- Methods: , start with a verb (, , , , , , )
- Variables: , descriptive nouns; Lists as plural nouns (e.g., , ); Maps as (e.g., ); Sets as
- Constants:
- Use full descriptive names instead of abbreviations (, , )
ApexDoc
- Required on the class header and every / method
- Include: brief description, , , , where helpful
Class-level format:
apex
/**
* Provides services for geolocation and address conversion.
*/
public with sharing class GeolocationService { }
Method-level format:
apex
/**
* @param paramName Description of the parameter
* @return Description of the return value
* @example
* List<Account> results = AccountService.deduplicateAccounts(accountIds);
*/
Code Structure & Architecture
- Single responsibility per class; max 500 lines -- split when exceeded
- Return Early: validate preconditions at method top, return/throw immediately
- Extract private helpers for methods over ~40 lines
- Use Dependency Injection (constructor/method params) for testability
- Prefer composition and narrow interfaces over deep inheritance; extend via new implementations, not modifications
- Enforce single-level abstraction per method across layer boundaries:
| Layer | Owns | Must NOT contain |
|---|
| Trigger | Event routing only | Business logic, orchestration |
| Handler/Service | Flow control, coordination | Inline SOQL/DML/HTTP/parsing |
| Domain | Business rules, validation | Queries, callouts, persistence details |
| Data/Integration | SOQL, DML, HTTP | Business decisions |
- Disallowed: methods mixing orchestration with inline SOQL/DML/HTTP; business rules mixed with parsing internals; validation + persistence + cross-system plumbing in one method
Async Decision Matrix
| Scenario | Default | Key Traits |
|---|
| Standard async work | Queueable | Job ID, chaining, non-primitive types, configurable delay (up to 10 min via ), dedup signatures |
| Very large datasets | Batch Apex | Chunked processing, max 5 concurrent; use for large scopes |
| Modern batch alternative | CursorStep () | 2000-record chunks, higher throughput, no 5-job limit |
| Recurring schedule | Scheduled Flow (preferred) or Schedulable | Schedulable has 100-job limit; use only when chaining to Batch or needing complex Apex logic |
| Post-job cleanup | Finalizer () | Runs regardless of Queueable success/failure |
| Long-running callouts | Continuation | Up to 3 per transaction, 3 parallel |
| Delays > 10 minutes | | Schedule a Batch job at a specific future time |
| Legacy fire-and-forget | | Do not use in new code — see Hard-Stop Constraints; replace with Queueable + Finalizer |
Type-Specific Guidance
Service
- Template: · Reference:
references/AccountService.cls
- ; stateless — no fields or mutable instance state; keep public APIs focused and where reasonable
- Delegate all SOQL to Selectors and SObject behavior to Domains
- Wrap business errors in a custom exception (e.g., )
Selector
- Template: · Reference:
references/AccountSelector.cls
- ; one per SObject or query domain
- Return or ; use a shared base field list constant (no inline duplication)
- Accept filter parameters; always include
Domain
- Template:
- ; encapsulate field defaults, derivations, and validations
- Operate on in-memory lists only; no SOQL/DML (belongs in Services/Selectors)
Batch
- Template: · Reference:
references/AccountDeduplicationBatch.cls
- ; implement
Database.Batchable<SObject>
(add when tracking across chunks)
- = query definition; = business logic; = logging/notification
- Use for large datasets; handle partial failures via
- Accept filter parameters via constructor for reusability
Queueable
- Template:
- ; implement and optionally when HTTP callouts are needed
- Accept data via constructor
- Add chain-depth guards to prevent infinite chains
- Optionally implement for recovery/cleanup
- Use for configurable delay (up to 10 min) and dedup signatures
Schedulable
- Template:
- ; delegates to Queueable or Batch
- Provide CRON constants and a convenience helper
DTO / Wrapper
- Template:
- No sharing keyword needed (pure data containers)
- Simple public properties; no-arg + parameterized constructors; when ordering matters
- Use on private/protected inner DTOs that are serialized/deserialized
Utility
- Template:
- No sharing keyword needed; all methods ; constructor
- Pure, side-effect-free; no SOQL/DML
Interface
- Template:
- Define clear contracts with ApexDoc on each method signature
Abstract
- Template:
- ; offer default behavior via methods
- Mark extension points or
- Include a concrete example in the ApexDoc showing how to extend the class
Custom Exception
- Template:
- No sharing keyword; extend with descriptive names
- Supported constructors: , , ,
Trigger
- Template:
- One trigger per object; delegate all logic to handler/TAF action classes
- Include all relevant DML contexts; if TAF:
new MetadataTriggerHandler().run();
Trigger Action (TAF)
- One class per concern per context; implement
- Register via (actions are inactive without registration)
- Name:
TA_{SObject}_{ActionName}
; prefer field-value comparison over static booleans for recursion
Invocable Method ()
- Template:
- ; inner / with
- Method must be ; non-static or single-object signatures will not compile
- Accept , return ; bulkify (SOQL/DML outside loops)
- Decorator parameters: (required — Flow Builder display name), , (groups actions in Builder), (required when method makes HTTP callouts)
- parameters: (required), ,
- supports: primitives, , , only (no //); use or fields for Flow collection I/O
- Always include , , and () in Response
- Return errors in Response (recommended); throwing an exception triggers the Flow Fault path — reserve for unrecoverable failures only
REST Resource ()
- Template:
- ; both class and methods must be
- Versioned URL:
@RestResource(urlMapping='/{resource}/v1/*')
- Use proper HTTP status codes per branch (/////); never default all errors to
- Validate inputs (Id format:
Pattern.matches('[a-zA-Z0-9]{15,18}', value)
); bind all user input in SOQL
- Include / in queries; implement pagination (/)
- Standardized wrapper (, , /); inner request/response DTOs
- Thin controller: delegate business logic to Service classes
Controller
- ; use in all SOQL
- Use
@AuraEnabled(cacheable=true)
only for read-only queries; leave unset for DML operations
- Catch exceptions and rethrow as with user-friendly messages
Output Expectations
Deliverables per class:
- (default API version or higher unless specified)
- (generated via skill)
{ClassName}Test.cls-meta.xml
(generated via skill)
Deliverables per trigger:
{TriggerName}.trigger-meta.xml
(default API version or higher unless specified)
Meta XML template:
xml
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>{API_VERSION}</apiVersion>
<status>Active</status>
</ApexClass>
Report in this order:
text
Apex work: <summary>
Files: <paths>
Design: <pattern / framework choices>
Workflow: all steps completed (1-8); any N/A justified
Risks: <security, bulkification, async, dependency notes>
Analyzer: <REQUIRED -- paste actual run_code_analyzer output or state "run_code_analyzer=unavailable: <reason>">
Testing: <REQUIRED -- paste actual test execution results (pass/fail, coverage) or state "test_execution=unavailable: <reason>">
Deploy: <dry-run or next step>
Cross-Skill Integration
| Need | Delegate to |
|---|
| Apex tests / fix failures | skill |
| Describe objects/fields | metadata skill (if available) |
| Deploy to org | deploy skill (if available) |
| Flow calling Apex | Flow skill (if available) |
| LWC calling Apex | LWC skill (if available) |
Troubleshooting Boundary
This skill handles production
/
/
issues only: compile/parse failures, deployment dependency errors, runtime governor-limit failures. For test execution, assertions, coverage, or
failures, delegate to
.