Org Skills

Apex

Always-on rules for writing Salesforce Apex classes, triggers, async jobs and test classes.

Raw Source
What it isAlways-on coding rules. GitHub Copilot applies them to files matching **/*.cls,**/*.trigger; the Claude Code version is a path-scoped rule generated from the same file. Why use instructions →

Install

This repository (recommended)

Adds .github/instructions/apex.instructions.md — commit it so everyone's Copilot follows the same rules.

npx -y github:AGCO-Global/org-skills add instructions apex

Just for me, every repository

Save it in your user profile instead.

npx -y github:AGCO-Global/org-skills add instructions apex --user

Use it

Nothing to invoke — edit a matching file and Copilot picks the rules up. Adjust applyTo if your repository layout differs.

This repository (recommended)

Adds .claude/rules/apex.md — commit it. Claude loads it when it reads a matching file.

npx -y github:AGCO-Global/org-skills add instructions apex --tool claude

Just for me, every repository

Save it in your user profile instead.

npx -y github:AGCO-Global/org-skills add instructions apex --tool claude --user

Use it

Nothing to invoke. Adjust the paths globs if your repository layout differs.

Commands run in your repository folder and need Node.js 20+. They use your normal git sign-in to GitHub, so they work while the repository is private. Run npx -y github:AGCO-Global/org-skills list to see everything available.

Settings

name: "Apex"
description: "Always-on rules for writing Salesforce Apex classes, triggers, async jobs and test classes."
applyTo: "**/*.cls,**/*.trigger"

Instructions Copilot follows

Apex rules

Write every method as if it receives 200 records, runs as a user with restricted sharing, and shares its transaction with other automation.

Bulkification and limits

  • Never put SOQL or DML inside a loop. Collect Ids, run one query into a Map<Id, SObject>, work in memory, then do one DML. Loops of queries hit the 100 SOQL limit.
  • Build child groupings (Map<Id, List<Child__c>>) in one pass; avoid nested loops over two large lists. O(n²) burns CPU time.
  • Aggregate in SOQL (GROUP BY, SUM, COUNT) instead of summing in Apex loops.
  • In triggers, compare Trigger.new with Trigger.oldMap and process only records whose relevant fields changed.
  • Set same-record fields in before context; no extra DML and no recursion.
  • Design against the per-transaction limits: 100 SOQL, 150 DML, 10,000 DML rows, 50,000 query rows, 10 s CPU, 6 MB heap (sync).
  • Use Database.insert(records, false) only when partial success is acceptable, and inspect every SaveResult.

Triggers and layering

  • One trigger per object, containing only a call into its handler (new OrderTriggerHandler().run();). Multiple triggers have no guaranteed order.
  • Handlers route by context and delegate; no business logic in the trigger or handler body.
  • Guard recursion with a static Set<Id> of processed records, not a static boolean. Booleans skip records in later chunks.
  • Put all SOQL in selector classes (OrderSelector.selectByIds(Set<Id>)) and business rules in service classes. Services are reusable from triggers, controllers, Flow and batch.
  • Keep @AuraEnabled and @InvocableMethod methods thin: validate input, call a service, return a DTO.
  • Invocable methods take and return a List<Request>/List<Result>. Flow calls them in bulk.

Security

  • Declare with sharing on every class; inherited sharing for utilities; without sharing only in a small, dedicated class with a comment explaining why.
  • Use WITH USER_MODE in SOQL and AccessLevel.USER_MODE for DML in user-facing paths so the platform enforces CRUD/FLS. SYSTEM_MODE needs a written reason.
  • Use bind variables (:accountIds) or Database.queryWithBinds; never concatenate input into SOQL. Prevents SOQL injection.
  • Validate everything from LWC or API callers: types, ranges, and that the user may access the given Ids.
  • No hard-coded Ids, profile names, record type Ids, URLs or emails. Use Custom Metadata, Custom Labels and getRecordTypeInfosByDeveloperName(); Ids differ per org.
  • Keep secrets in Named Credentials or protected settings, never in code.

Async Apex

  • Queueable for post-commit work and callouts from triggers; Batch Apex for large volumes; Schedulable only to enqueue a Queueable or Batch.
  • Do not use @future in new code. It cannot chain or take complex types.
  • Make async jobs idempotent; they can run more than once. Chain rather than fan out to stay under queued-job limits.
  • Publish Platform Events for cross-system decoupling; subscribers must tolerate duplicate delivery.
  • Split setup-object DML (User, PermissionSet) into a Queueable to avoid MIXED_DML_OPERATION.

Errors and logging

  • Catch specific exceptions; never leave a catch empty.
  • Throw AuraHandledException with a user-safe message at the controller boundary; log the real error with its record Ids.
  • Log through the project's logger (persisted via a custom object or Platform Event), not System.debug. Logs must survive rollback.
  • Use Database.setSavepoint() and Database.rollback() around multi-step DML that must be atomic.

Tests

  • One @IsTest class per class, data from a TestDataFactory in @TestSetup; never SeeAllData=true.
  • Cover positive, bulk (200+ records), negative and System.runAs sharing cases.
  • Wrap the call in Test.startTest()/Test.stopTest() to reset limits and run async work.
  • Assert outcomes with messages (Assert.areEqual(expected, actual, 'status after approval')); a test without asserts proves nothing.
  • Mock callouts with HttpCalloutMock and services with interfaces or Test.createStub.
  • Treat 75% coverage as a floor; aim for 85% or more on services and selectors.

Go deeper: for larger tasks use the apex-development, salesforce-automation, salesforce-data-model and salesforce-integration skills, and backend-code-review before opening a pull request.