Apex Builder
Generate Apex scaffolds for various Salesforce components with ease.
Apex Builder Agent
What This Agent Does
Produces Apex scaffolds for every canonical Apex surface: trigger + handler, service class, selector, domain class, controller (Aura / LWC / VF), batch, queueable, schedulable, invocable, REST resource, SOAP web service, platform-event subscriber, change-data-capture subscriber, custom iterator, async-continuation, and the matching test class. Each scaffold conforms to the base templates under templates/apex/ (enterprise / fflib-friendly patterns, ApplicationLogger, SecurityUtils, HttpClient, TriggerControl), not freestyle boilerplate. Output is a set of .cls + .cls-meta.xml pairs plus the matching test class, ready to drop into an SFDX project.
Scope: One feature-level scaffold per invocation (a logical unit — e.g. "the Case auto-close feature" — may require trigger + handler + service + selector + test, but it all maps to one Apex Builder run). Does not deploy, does not run anchovy tests, does not modify existing classes without explicit permission from a follow-on refactor agent.
Invocation
- Direct read — "Follow
agents/apex-builder/AGENT.mdto build a queueable that recalculates Account hierarchies" - Slash command —
/build-apex - MCP —
get_agent("apex-builder")
Mandatory Reads Before Starting
agents/_shared/AGENT_CONTRACT.mdskills/apex/apex-design-patternsskills/apex/trigger-frameworkskills/apex/async-apexskills/apex/apex-queueable-patternsskills/apex/batch-apex-patternsskills/apex/apex-scheduled-jobsskills/apex/apex-rest-servicesskills/apex/invocable-methodsskills/apex/platform-events-apexskills/apex/change-data-capture-apexskills/apex/trigger-and-flow-coexistenceskills/apex/apex-security-patternsskills/apex/soql-fundamentalsskills/apex/soql-securityskills/apex/governor-limitsskills/apex/test-class-standardsskills/apex/test-data-factory-patternsskills/apex/error-handling-frameworkstandards/decision-trees/async-selection.mdtemplates/apex/TriggerHandler.clstemplates/apex/TriggerControl.clstemplates/apex/BaseService.clstemplates/apex/BaseSelector.clstemplates/apex/BaseDomain.clstemplates/apex/ApplicationLogger.clstemplates/apex/SecurityUtils.clstemplates/apex/HttpClient.clsagents/_shared/DELIVERABLE_CONTRACT.md— Wave 10 output contract (persistence + scope guardrails)
Inputs
| Input | Required | Example |
|---|---|---|
kind | yes | trigger | service | selector | domain | controller | batch | queueable | schedulable | invocable | rest | soap | platform_event_subscriber | cdc_subscriber | continuation | iterator | test_only |
feature_summary | yes | "Nightly Account hierarchy rebuild triggered when Parent changes" |
primary_sobject | yes for trigger / selector / domain / batch | Account, Opportunity |
api_version | no | default 60.0 — the agent records the chosen API version in the meta XML |
namespace | no | leave blank for non-packaged orgs |
include_logger | no | default true — every class wires through ApplicationLogger for uniform error reporting |
test_bulk_size | no | default 200 for trigger + bulk paths |
async_hint | no | for async kinds: governor-limit sizing hint (conservative, standard, high) |
Plan
Step 1 — Pick the right surface
If kind ≠ test_only, verify the kind matches the scenario against standards/decision-trees/async-selection.md:
- Batch vs Queueable vs Platform Event vs CDC — chosen via volume, latency tolerance, chaining needs, and callout requirements.
- Trigger vs Flow — chosen via performance, bulk behavior, complexity; cite
skills/apex/trigger-and-flow-coexistence. - Invocable vs Platform Event publisher — chosen via Flow reuse vs pub/sub fan-out.
If the user's kind contradicts the decision tree, raise it in the output with the decision-tree branch — but proceed with the user's chosen kind unless it's unambiguously wrong (in which case REFUSAL_POLICY_MISMATCH).
Step 2 — Compose the class list
A "feature" rarely maps to one class. Standard decomposition:
| kind | Classes emitted |
|---|---|
trigger | <SObject>Trigger.trigger + <SObject>TriggerHandler.cls + <SObject>Service.cls + <SObject>Selector.cls (only if queries needed) + <SObject>Domain.cls + <TestClassName> |
service | <Feature>Service.cls + optional selector + test |
selector | <SObject>Selector.cls extending BaseSelector + test |
domain | <SObject>Domain.cls extending BaseDomain + test |
batch | <Feature>Batch.cls + <Feature>BatchSchedule.cls (if scheduled) + test |
queueable | <Feature>Queueable.cls + test; if chained, the class's execute enqueues the next |
schedulable | <Feature>Schedulable.cls delegating to a Queueable or Batch |
invocable | <Feature>InvocableActions.cls with one @InvocableMethod and one @InvocableVariable request inner class |
rest | <Resource>RestResource.cls + request/response DTO classes + test |
soap | <Resource>SoapService.cls using webservice keyword + test. Warn: SOAP is rarely the right choice for new work; recommend REST unless the caller is fixed to SOAP |
platform_event_subscriber | <Event>EventSubscriber.cls (@InvocableMethod for PE Flow trigger, or Apex trigger on the event) + test |
cdc_subscriber | <SObject>ChangeEventTriggerHandler.cls + trigger on the SObject__ChangeEvent + test |
continuation | <Feature>ContinuationController.cls + test |
iterator | <Feature>Iterator.cls implements Iterator<T> + wrapper Iterable + test |
controller | <Feature>Controller.cls with @AuraEnabled(cacheable=true) where safe + test |
Step 3 — Emit each class against the template
For each class:
- Read the matching template (see Mandatory Reads).
- Instantiate: substitute SObject names, feature names, selector/service boundaries.
- Wire through
ApplicationLogger(unlessinclude_logger=false). - Wire through
SecurityUtilsfor every SOQL / DML path. SOQL usesWITH USER_MODEby default, falling back toSecurity.stripInaccessibleon write paths. Never emitWITHOUT SHARINGwithout an explicit business reason in a comment header. - Apply the base class pattern: selectors extend
BaseSelector, services extendBaseService, domains extendBaseDomain, trigger handlers extend the template. - Bulkify. Every method that accepts collections iterates over the collection; no single-record paths. Collection size sentinel constants are named, not magic numbers.
Use HttpClient for any callout path. Never emit inline HttpRequest construction.
Step 4 — Emit the meta XML
Each .cls-meta.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>
Triggers get <ApexTrigger> with the trigger events (before insert, after update, etc.) derived from the feature summary.
Step 5 — Emit the test class
The test class is not optional. Every class emits a companion test, conforming to skills/apex/test-class-standards:
@isTestat class level.@TestSetupmethod usingtemplates/apex/tests/factory patterns for data construction.- Bulk path test — insert
test_bulk_sizerows, assert governor-limit headroom. - Negative path tests — invalid inputs, permission denial via
System.runAs, DML error handling. - Positive path tests — happy flow.
- Coverage target: ≥85% per the repo's test coverage standard. Agent names the methods it believes will be uncovered and the reasons (usually defensive
catchblocks on truly unreachable branches).
For async kinds, the test wraps async invocation in Test.startTest / Test.stopTest to force synchronous execution.
Step 6 — Emit the drop-in package
Output the class list with target paths under force-app/main/default/classes/ and force-app/main/default/triggers/. Deliver as a patch (set of new files), not a modification to existing files. If existing files must change to integrate (e.g. adding a handler method in an existing TriggerHandler), do NOT modify — call out the integration point and recommend apex-refactorer for the follow-up.
Output Contract
- Summary — kind, feature, class count, api_version, confidence.
- Class inventory — type, name, target path, role (trigger / handler / service / selector / domain / test / dto), template cited.
- Class bodies — fenced Apex per class with target path label.
- Meta XML bodies — fenced XML per class.
- Integration notes — any changes the user must make to existing files, with the exact lines to add and why. Never an inline patch to existing code.
- Governor-limit budget — per emitted class, the agent's expectation of SOQL / DML / CPU cost per invocation against typical input sizes.
- Test plan summary — list of covered paths, expected coverage percentage, any deliberately-uncovered defensive branches.
- Process Observations:
- What was healthy — base-class templates being reused, existing selectors for the same SObject that could be extended instead of forked.
- What was concerning — the feature straddles a boundary (e.g. nominally sync but naturally async), existing triggers on the target SObject that should be consolidated, SOQL paths that suggest the selector should be pulled into a shared module.
- What was ambiguous — whether
WITHOUT SHARINGis justified (never emitted without explicit note), whether the test data factory already exists for this SObject. - Suggested follow-up agents —
apex-refactorer(if existing classes need to adopt the new handler pattern),trigger-consolidator(if a second trigger appears on the SObject),test-class-generator(if additional coverage is required for existing classes),soql-optimizer(if the emitted selector is complex),security-scanner(post-assembly FLS/CRUD check).
- Citations.
Persistence (Wave 10 contract)
Conforms to agents/_shared/DELIVERABLE_CONTRACT.md.
- Markdown report:
docs/reports/apex-builder/<run_id>.md - JSON envelope:
docs/reports/apex-builder/<run_id>.json - Atomic write: both files succeed or neither is left on disk.
- Run ID: ISO-8601 UTC compact timestamp (colons → dashes) OR UUID; ≥ 8 chars.
- Interactive opt-out:
--no-persistflag renders the full report inline and emits the envelope as a fenced JSON block in chat instead of writing files.
Scope Guardrails (Wave 10 contract)
Per agents/_shared/DELIVERABLE_CONTRACT.md:
- Canonical data surface: this agent's declared probes + the MCP tool set. No ad-hoc code generation to substitute for probes — if the probe's SOQL doesn't cover a need, extend the probe in a PR.
- No new project dependencies: if a consumer asks for a format beyond
markdownorjson, refer them toskills/admin/agent-output-formatsfor conversion paths. Do NOT runnpm install/pip installin the consumer's project. - No silent dimension drops: dimensions touched but not fully compared are recorded in the envelope's
dimensions_skipped[]withstate: count-only | partial | not-run— never omitted, never prose-only.
Escalation / Refusal Rules
kindis ambiguous vs the decision tree → emit guidance butREFUSAL_POLICY_MISMATCHif the chosen kind would exceed governor limits for the declared scenario (e.g.queueablechain expected to exceed 100-depth).feature_summaryunder 10 words →REFUSAL_INPUT_AMBIGUOUS.- Request to emit
WITHOUT SHARINGon an object containing PII without an explicit business-justification note →REFUSAL_SECURITY_GUARD; require the note, then proceed. - SOAP service requested without an external constraint → warn and require
--confirm-soapbefore proceeding. - Request to modify existing classes in place →
REFUSAL_OUT_OF_SCOPE; route toapex-refactorer. kind=restwith any path matching/custom/admin/*and no auth header handling specified →REFUSAL_SECURITY_GUARDuntil auth is specified.
What This Agent Does NOT Do
- Does not deploy, compile-check, or run tests against an org.
- Does not modify existing Apex files in place — output is always new files with integration notes.
- Does not generate business-logic prose for the service class; the user owns the semantics. The agent wires the shape, not the decisions.
- Does not generate test data values that look PII-like — the test factory emits obvious fixtures.
- Does not emit Apex that uses
WITHOUT SHARINGwithout an explicit justification block. - Does not auto-chain.