25 KiB
| name | description |
|---|---|
| generating-apex | Primary Apex authoring skill for class generation, refactoring, and review. ALWAYS ACTIVATE when the user mentions Apex, .cls, triggers, or asks to create/refactor a class (service, selector, domain, batch, queueable, schedulable, invocable, DTO, utility, interface, abstract, exception, REST resource). Use this skill for requests involving SObject CRUD, mapping collections, fetching related records, scheduled jobs, batch jobs, trigger design, @AuraEnabled controllers, @RestResource endpoints, custom REST APIs, or code review of existing Apex. |
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 .cls.
When This Skill Owns the Task
- Any Apex class generation or refactor (service, selector, domain, DTO/wrapper, utility, interface, abstract, exception)
- Trigger design and trigger-framework decisions
- Async or orchestration: Batch, Queueable (incl. Finalizer), Schedulable, CursorStep,
@InvocableMethod @AuraEnabledcontrollers for LWC/Aura@RestResourceendpoints for custom REST APIs- Review of bulkification, sharing, security, or maintainability
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
Defaults unless specified:
- Sharing:
with sharing(see sharing rules per type below) - Access:
public(useglobalonly when required by managed packages or@InvocableMethod) - API version:
66.0(minimum version) - ApexDoc comments: yes
Workflow
All steps in this workflow are MANDATORY and must be executed in order. Execute every step without skipping, merging, or reordering. If a step is blocked or seemingly not applicable, STOP and request the missing context, or explicitly mark it as "N/A" with a one-sentence justification in the final report before proceeding. Continue only when the gate for the current step is satisfied.
CRITICAL -- WORKFLOW INTEGRITY RULES:
- NEVER remove, rename, or consolidate checklist items from your task progress. Every step listed below must appear in task_progress from start to finish.
- The task is NOT complete after writing files. Writing
.clsand.cls-meta.xmlfiles is the MIDPOINT of this workflow, not the end. Steps 6 and 7 are mandatory tool invocations that MUST execute before completion. - NEVER call
attempt_completionor present a final summary until Steps 6, 7, and 8 are all executed and documented with their actual tool outputs.
-
[MANDATORY] 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
-
[MANDATORY] Choose the smallest correct pattern (see Type-Specific Guidance below)
-
[MANDATORY] Review templates and assets
- Check this skill's
templates/andassets/ - For any test class work, always read and use
generating-apex-testskill
- Check this skill's
-
[MANDATORY] Author with guardrails -- apply every rule in the Rules section below
- Generate
{ClassName}.clswith ApexDoc - Generate
{ClassName}.cls-meta.xml
- Generate
-
[MANDATORY] Generate test classes -- STOP: Do not write any apex test code in this skill. Immediately activate
generating-apex-testskill and follow its complete workflow to generate{ClassName}Test.clsand{ClassName}Test.cls-meta.xml. Only return here after that skill reports completion; if activation is unavailable, abort this step and recordtest_skill=unavailable: <reason>in Step 8.
EXECUTION GATE -- DO NOT SKIP STEPS 6 AND 7
You have written files. You are NOT done. The two steps below require MCP tool invocations and CLI commands -- they are not file writes. You MUST execute them now. Do NOT call attempt_completion, do NOT present a summary, and do NOT say the task is complete until both Step 6 and Step 7 have been executed and their outputs are recorded.
-
[MANDATORY] run_code_analyzer MCP tool -- DO NOT SKIP -- REQUIRES TOOL INVOCATION
- You MUST call the MCP tool
run_code_analyzerRIGHT NOW to performe static analysis against code on the newly generated/updated.clsfiles. - This is a tool invocation, not a file write. Invoke
run_code_analyzeras an MCP tool call. - Remediate all violations with severity levels
sev0,sev1, andsev2. - Re-run
run_code_analyzeruntil nosev0-sev2issues remain. - Record the final
run_code_analyzeroutput (clean or with remaining sev3+ only) -- you will need it for the report in Step 8. - If the MCP tool is unavailable after a real invocation attempt cli command
sf code-analyzer run --target <target>, if that also doesnt work recordrun_code_analyzer=unavailablewith the error and state this in the report. Do NOT silently skip.
- You MUST call the MCP tool
-
[MANDATORY] Execute Apex tests -- DO NOT SKIP -- REQUIRES TOOL INVOCATION
- You MUST run the org's Apex test suite RIGHT NOW, including
{ClassName}Test. - Execute tests using the
sf apex run testCLI command or the appropriate MCP tool. - All test authoring, failure remediation, and coverage improvements MUST be performed by reading and following
generating-apex-testskill; iterate until green. - Record the test execution results (pass/fail counts, coverage percentage) -- you will need them for the report in Step 8.
- If test execution is unavailable (no org connected, no CLI access), record
test_execution=unavailablewith the error and state this in the report. Do NOT silently skip.
- You MUST run the org's Apex test suite RIGHT NOW, including
-
[MANDATORY] Report -- use the output format at the bottom of this file.
- The
Analyzerline MUST include actualrun_code_analyzeroutput from Step 6, or explicitly staterun_code_analyzer=unavailablewith the reason. - The
Testingline MUST include actual test execution results from Step 7, or explicitly statetest_execution=unavailablewith the reason. - NEVER omit the Analyzer or Testing lines. NEVER write "N/A" without having attempted the tool invocation first.
- The
-
[MANDATORY] Enforce cross-skill boundaries
- Test class creation, updates, refactors, data setup, and advanced fixtures: ALWAYS delegate by reading and following
generating-apex-testskill - NEVER write test code directly in this skill; all test work flows through the test skill
- Test class creation, updates, refactors, data setup, and advanced fixtures: ALWAYS delegate by reading and following
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 without sharing 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 (List, Map, Set) rather than Java types |
Prevent compile errors |
| Verify methods exist in Apex before use | Prevent reliance on non-existent APIs |
Use Assert class instead of System.assert* in test classes |
Legacy System.assert, System.assertEquals, System.assertNotEquals are deprecated; use Assert.areEqual, Assert.isTrue, Assert.fail, etc. |
Avoid System.debug() in main code paths |
Debug statements that concatenate variables into the string consume CPU regardless of logging being enabled; use a logging framework or Custom Metadata-controlled logger instead if required on main code paths |
Never use @future methods |
Use Queueable with System.Finalizer for all async work; @future cannot be called from Batch, cannot chain, and cannot accept non-primitive types |
Bulkification & Governor Limits
- Collect all SOQL and DML outside of loops; operate on
List,Set,Map - 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 processSaveResultfor errors - Use
Map<Id, SObject>constructor for efficient ID-based lookups from query results - Use
Set<Id>for deduplication and membership checks; preferSet.contains()overList.contains()
SOQL Optimization
- Use selective queries with proper
WHEREclauses; use indexed fields (Id,Name,OwnerId, lookup/master-detail fields,ExternalIdfields, custom indexes) in filters when possible SELECT *does not exist in SOQL -- always specify the exact fields needed- Apply
LIMITclauses to bound result sets; useORDER BYfor deterministic results - When querying Custom Metadata Types (objects ending with
__mdt), do NOT use SOQL — use the built-in methods ({CustomMdt__mdt}.getAll().values(),getInstance(), etc.)
Security
- Default to
with sharing; document the justification whenwithout sharingorinherited sharingis chosen - Use
WITH USER_MODEin SOQL queries for automatic CRUD/FLS enforcement - Use bind variables (
:variableName) for all dynamic SOQL; sanitize queries by binding variables rather than concatenating user input - For dynamic field/operator names in SOQL, validate against an allowlist or
Schema.describebefore use - Use
Security.stripInaccessible()whenWITH USER_MODEis not available (pre-API 62.0) - Store credentials and API keys in Named Credentials and reference them in code
- Use
AuraHandledExceptionfor user-facing errors in@AuraEnabledmethods; ensure messages are user-safe and exclude internal details - When using
without sharing, add a Custom Permission check to restrict access
Error Handling
- Catch specific exceptions before generic
Exception; include context in messages - Only wrap code in
try/catchwhen the enclosed statements can realistically throw -- never add defensive try/catch around simple field assignments, collection additions, or arithmetic; place error handling around DML, callouts, JSON parsing, and type casting where failures are expected - Preserve exception cause chains: use
new CustomException('message', causedException)-- preserve the original stack trace by passing the cause rather than concatenatinge.getMessage()into a new exception - Provide a custom exception class per service domain when meaningful
- In
@AuraEnabledmethods, catch exceptions and rethrow asAuraHandledException
Null Safety
- Add guard clauses for null/empty inputs at the top of every public method
- Return empty collections instead of
null - Use safe navigation (
?.) for chained property access - Use null coalescing (
??) for default values - Prefer
String.isBlank(value)over manual checks likevalue == null || value.trim().isEmpty()
Constants & Literals
- Use enums over string constants whenever possible; enum values follow
UPPER_SNAKE_CASE - Extract all literal strings and numbers into
private static finalconstants or a dedicated constants class - Use
Label.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 | {SObject}Service |
AccountService |
| Selector | {SObject}Selector |
AccountSelector |
| Domain | {SObject}Domain |
OpportunityDomain |
| Batch | {Descriptive}Batch |
AccountDeduplicationBatch |
| Queueable | {Descriptive}Queueable |
ExternalSyncQueueable |
| Schedulable | {Descriptive}Schedulable |
DailyCleanupSchedulable |
| DTO | {Descriptive}DTO |
AccountMergeRequestDTO |
| Wrapper | {Descriptive}Wrapper |
OpportunityLineWrapper |
| Utility | {Descriptive}Util |
StringUtil |
| Interface | I{Descriptive} |
INotificationService |
| Abstract | Abstract{Descriptive} |
AbstractIntegrationService |
| Exception | {Descriptive}Exception |
AccountServiceException |
| REST Resource | {SObject}RestResource |
AccountRestResource |
| Trigger | {SObject}Trigger |
AccountTrigger |
| Trigger Action | TA_{SObject}_{Action} |
TA_Account_SetDefaults |
Additional naming rules:
- Classes:
PascalCase - Methods:
camelCase, start with a verb (get,create,process,validate,is,has) - Variables:
camelCase, descriptive nouns; Maps as{value}By{key}(e.g.,accountsById); Sets as{noun}Ids - Constants:
UPPER_SNAKE_CASE - Use full descriptive names instead of abbreviations (
acc,tks,rec)
ApexDoc
- Required on the class header and every
public/globalmethod - Include: brief description,
@param,@return,@throws,@examplewhere helpful
Modern Apex Idioms
Prefer current language features:
- Safe navigation:
obj?.Field__c - Null coalescing:
value ?? fallback WITH USER_MODEover manual CRUD/FLS checksDatabase.Cursor(CursorStep) over Batch Apex for new high-throughput jobsAssertclass (Assert.areEqual,Assert.isTrue,Assert.fail, etc.) over legacySystem.assert,System.assertEquals,System.assertNotEqualsin all test classes
Code Structure
- Keep each class focused on a single responsibility
- Limit class size to 500 lines of code maximum; split into collaborating classes when exceeded
- Use the Return Early pattern -- validate preconditions at the top of methods and return/throw immediately to reduce nesting
- Extract private helpers for methods longer than ~40 lines
- Prefer interfaces for cross-class contracts to keep coupling loose
- Use Dependency Injection (constructor or method parameters) to decouple collaborators and improve testability
- Group related classes in packages/folders when possible (e.g., all Account-related classes together)
- Maintain consistent abstraction levels within a method -- keep orchestration separate from low-level implementation
Async Decision Matrix
| Scenario | Default | Key Traits |
|---|---|---|
| Standard async work | Queueable | Job ID, chaining, non-primitive types, configurable delay (up to 10 min via AsyncOptions), dedup signatures |
| Very large datasets | Batch Apex | Chunked processing, max 5 concurrent; use QueryLocator for large scopes |
| Modern batch alternative | CursorStep (Database.Cursor) |
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 (System.Finalizer) |
Runs regardless of Queueable success/failure |
| Long-running callouts | Continuation | Up to 3 per transaction, 3 parallel |
| Legacy fire-and-forget | @future |
Do not use -- replace with Queueable + Finalizer in all new development |
| Delays > 10 minutes | System.scheduleBatch() |
Schedule a Batch job at a specific future time |
Type-Specific Guidance
Service
with sharing; stateless; keep public APIs focused andstaticwhere reasonable- Delegate all SOQL to Selectors and SObject behavior to Domains
- Wrap business errors in a custom exception (e.g.,
AccountServiceException)
Selector
inherited sharing(inherits from caller -- allows reuse from bothwithandwithout sharingcontexts)- One Selector per SObject or query domain
- Return
List<SObject>orMap<Id, SObject>; maintain a DRY base field list constant and reference it in all SOQL queries -- never duplicate the field list inline across methods - Accept filter criteria as parameters; always include
WITH USER_MODE
Domain
with sharing; encapsulate SObject field defaults, derivations, and validations- Operate only on in-memory lists; perform SOQL and DML in Services/Selectors
- Designed to be invoked from services, triggers, and orchestration layers
Batch
with sharing; implementDatabase.Batchable<SObject>(addDatabase.Statefulwhen tracking results across chunks)- Keep
start()focused on query definition; place business logic inexecute() - Use
QueryLocatorfor large datasets; handle partial failures viaDatabase.SaveResult - Provide a meaningful
finish()-- at minimum logging; consider notifications - Accept filter parameters via constructor to make the batch reusable
Queueable
with sharing; accept data via constructor- Add chain-depth guards to prevent infinite chains
- Optionally implement
Finalizerfor recovery/cleanup - Use
AsyncOptionsfor configurable delay (up to 10 min) and dedup signatures
Schedulable
with sharing; keepexecute()lightweight -- delegate to a Queueable or Batch- Provide CRON expression constants; document schedule intent
- Provide a convenience
scheduleDaily()or similar static helper
DTO / Wrapper
- No sharing keyword needed (pure data containers; SOQL and DML are handled elsewhere)
- Prefer simple public properties; provide no-arg and parameterized constructors
- Serialization-friendly (
JSON.serialize/deserialize); implementComparablewhen ordering matters
Utility
- No sharing keyword needed (utility classes are pure functions; SOQL/DML belong in Services/Selectors)
- All methods
public static; provide aprivateconstructor to prevent instantiation - Keep all methods pure and side-effect-free; perform SOQL and DML in dedicated layers
Interface
- Define clear contracts with ApexDoc on each method signature
Abstract
with sharing; offer default behavior viavirtualmethods- Mark extension points
protected virtualorprotected abstract
Custom Exception
- No sharing keyword needed
- Extend
Exception; keep simple with descriptive names - Apex exceptions support:
new MyException(),new MyException('msg'),new MyException(cause),new MyException('msg', cause)
Trigger
- One trigger per object; delegate all logic to handler or TAF action classes
- Include all relevant DML contexts (
before insert, after insert, before update, after update, before delete, after delete, after undelete) - If TAF is installed, the trigger body is a single line:
new MetadataTriggerHandler().run();
Trigger Action (TAF)
- One class per concern per context; implement
TriggerAction.{Context}(e.g.,TriggerAction.BeforeInsert) - Register via
Trigger_Action__mdtcustom metadata records (actions do nothing without registration) - Name:
TA_{SObject}_{ActionName} - For recursion prevention, prefer field-value comparison over static boolean flags
Invocable Method (@InvocableMethod)
with sharing; use innerRequest/Responseclasses with@InvocableVariable- Accept
List<Request>, returnList<Response>; bulkify -- query and DML outside loops - Always include
isSuccess(Boolean) anderrorMessage(String) in Response - Return errors in Response rather than throwing exceptions (exceptions trigger Flow fault path)
- Limit supported
@InvocableVariabletypes to primitives,Id,SObject, andList<T>(types such asMap,Set, andBlobare not supported)
REST Resource (@RestResource)
global with sharing;globalis required for Apex REST endpoints -- both the class and annotated methods must beglobal- Use versioned URL mapping:
@RestResource(urlMapping='/{resource}/v1/*')for future API evolution - Parse
RestContext.requestdirectly in methods for flexibility; usevoidmethods withRestContext.responsewhen fine-grained status code control is needed, or return a response DTO for automatic serialization - Return appropriate HTTP status codes per logic branch:
200success,201created,400bad request,404not found,422validation failure,500internal error -- never default to500for all errors - Validate incoming parameters; use
Pattern.matches('[a-zA-Z0-9]{15,18}', value)for Id format validation; escape/bind all user input in SOQL - Always include
LIMITandORDER BYin SOQL queries; implement pagination viapageSize/offsetquery parameters with a reasonable max page size - Use
WITH USER_MODEin all SOQL for CRUD/FLS enforcement; combine withwith sharingfor record-level security - Design endpoints to handle bulk operations efficiently -- accept and return collections where appropriate
- Provide a standardized
ApiResponsewrapper withsuccess,message, anddata/recordsfields for consistent client parsing - Include inner request/response DTO classes to define the API contract clearly
- Delegate business logic to Service classes; keep the REST resource as a thin controller layer
@AuraEnabled Controller
with sharing; useWITH USER_MODEin all SOQL- Use
@AuraEnabled(cacheable=true)only for read-only queries; leavecacheableunset for DML operations - Catch exceptions and rethrow as
AuraHandledExceptionwith user-friendly messages
Output Expectations
Deliverables per class:
{ClassName}.cls{ClassName}.cls-meta.xml(default API version66.0or higher unless specified){ClassName}Test.cls(generated viagenerating-apex-testskill){ClassName}Test.cls-meta.xml(generated viagenerating-apex-testskill)
Meta XML template:
<?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:
Apex work: <summary>
Files: <paths>
Design: <pattern / framework choices>
Workflow: all mandatory steps completed (1-9); 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 | Reason |
|---|---|---|
| Describe objects / fields first | metadata skill | Ensure alignment with the correct schema |
| Seed bulk or edge-case data | data skill | Create realistic datasets |
| Run Apex tests / fix failing tests | Read and follow generating-apex-test skill |
Execute and iterate on failures |
| Deploy to org | deploy skill | Validation and deployment orchestration |
| Build Flow that calls Apex | Flow skill | Declarative orchestration via @InvocableMethod |
| Build LWC that calls Apex | LWC skill | UI/controller integration via @AuraEnabled |
LSP Validation
This skill supports an LSP-assisted authoring loop for .cls and .trigger files:
- Syntax issues detected immediately after write/edit
- Auto-fix common syntax errors in a short loop (max 3 attempts)
- Semantic quality validated via the code review checklist
Full guide: troubleshooting
Optional Enhancers
- Run additional PMD rulesets beyond the default
run_code_analyzerconfiguration for deeper style analysis
Reference Map
Core guides
- security guide -- CRUD/FLS, sharing, SOQL injection, Named Credentials
- bulkification guide -- governor limits, collection patterns, performance monitoring
Checklists & catalogs
- anti-patterns -- critical anti-patterns, code smells, and refactoring strategies
- naming conventions -- class, method, variable, collection naming
- llm anti-patterns -- hallucinated methods, Java types, null safety pitfalls
Specialized patterns
- trigger-actions-framework -- TAF setup, action classes, bypass, recursion
- automation-density guide -- Flow vs Apex vs hybrid decision framework
- flow integration --
@InvocableMethod/@InvocableVariablepatterns - triangle pattern -- Flow-LWC-Apex integration (Apex perspective)
- design patterns -- Factory, Strategy, Singleton, Builder, Decorator, Observer, Command, Facade, Domain, UoW
- solid principles -- SRP, OCP, LSP, ISP, DIP in Apex
Additional references
- best practices -- platform cache, static caching, guard clauses, comment guidelines
- troubleshooting -- LSP validation, deployment errors, debug logs, governor limit debugging