salesforce-apex-quality — GitHub Copilot skill: Apex quality
Checks Apex for governor-limit risks, security gaps, and missing tests. Use it when GitHub Copilot reviews or generates Salesforce classes, triggers, and test code.
npx skills add github/awesome-copilot --skill salesforce-apex-qualityWhat salesforce-apex-quality does
This skill reviews Apex classes, trigger handlers, batch jobs, and test classes. It flags SOQL or DML inside loops, requires an explicit sharing declaration, checks CRUD/FLS enforcement, and prevents user input from being joined into dynamic SOQL. It also asks for current Apex idioms and PNB coverage across Positive, Negative, and Bulk paths.
The rules cover trigger structure as well: one trigger per object, a small trigger body, and reuse of an existing framework when the project already has one. Bulk tests should process 200–251 records, while async work should use Test.startTest() and Test.stopTest(). The modern syntax guidance targets API 62.0 / Winter '25+.
Install it when GitHub Copilot is writing or reviewing Salesforce Apex. Check the API version first if the project predates the listed modern syntax.
What it helps with
- catch SOQL and DML inside loops
- enforce CRUD/FLS before user-facing data access
- block user-controlled SOQL injection
- cover Positive, Negative, and Bulk test paths
- review Salesforce trigger architecture
Example prompts for the agent
- Review this Apex class for governor-limit risks, CRUD/FLS, and SOQL injection.
- Generate an AccountService test class with Positive, Negative, and Bulk paths.
- Check this trigger handler for sharing model and bulk-safety issues.
How to install salesforce-apex-quality
1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill salesforce-apex-quality2GitHub CLI
gh skill install github/awesome-copilot salesforce-apex-quality3Manually: put the files in place
.github/skills/salesforce-apex-quality/SKILL.md1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill salesforce-apex-quality -a claude-code2Manually: put the files in place
~/.claude/skills/salesforce-apex-quality/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/salesforce-apex-quality/
Manually: put the files in place
.agents/skills/salesforce-apex-quality/SKILL.mdFor all projects use ~/.agents/skills/
Manually: put the files in place
.cursor/skills/salesforce-apex-quality/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
Platform CLI
gemini skills install https://github.com/github/awesome-copilot --path skills/salesforce-apex-qualityManually: put the files in place
.devin/skills/salesforce-apex-quality/SKILL.mdThe legacy .windsurf/skills/ path still works
Claude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set554
The whole set →Similar
Questions about salesforce-apex-quality
How do I install salesforce-apex-quality in GitHub Copilot?
Run npx skills add github/awesome-copilot --skill salesforce-apex-quality. The skill is then available at .github/skills/salesforce-apex-quality/SKILL.md. You can also run gh skill install github/awesome-copilot salesforce-apex-quality.
What does salesforce-apex-quality do?
It checks Apex for governor-limit risks, sharing declarations, CRUD/FLS, SOQL injection, trigger structure, and PNB test coverage. It applies to classes, triggers, batch jobs, and test classes.
Which Apex API version is used for the modern idioms?
The rules list API 62.0 / Winter '25+ for features such as optional chaining, null coalescing, and WITH USER_MODE.
Source text
SKILL.md · MIT
---
name: salesforce-apex-quality
description: 'Apex code quality guardrails for Salesforce development. Enforces bulk-safety rules (no SOQL/DML in loops), sharing model requirements, CRUD/FLS security, SOQL injection prevention, PNB test coverage (Positive / Negative / Bulk), and modern Apex idioms. Use this skill when reviewing or generating Apex classes, trigger handlers, batch jobs, or test classes to catch governor limit risks, security gaps, and quality issues before deployment.'
---
# Salesforce Apex Quality Guardrails
Apply these checks to every Apex class, trigger, and test file you write or review.
## Step 1 — Governor Limit Safety Check
Scan for these patterns before declaring any Apex file acceptable:
### SOQL and DML in Loops — Automatic Fail
```apex
// ❌ NEVER — causes LimitException at scale
for (Account a : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :a.Id]; // SOQL in loop
update a; // DML in loop
}
// ✅ ALWAYS — collect, then query/update once
Set<Id> accountIds = new Map<Id, Account>(accounts).keySet();
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
if (!contactsByAccount.containsKey(c.AccountId)) {
contactsByAccount.put(c.AccountId, new List<Contact>());
}
contactsByAccount.get(c.AccountId).add(c);
}
update accounts; // DML once, outside the loop
```
Rule: if you see `[SELECT` or `Database.query`, `insert`, `update`, `delete`, `upsert`, `merge` inside a `for` loop body — stop and refactor before proceeding.
## Step 2 — Sharing Model Verification
Every class must declare its sharing intent explicitly. Undeclared sharing inherits from the caller — unpredictable behaviour.
| Declaration | When to use |
|---|---|
| `public with sharing class Foo` | Default for all service, handler, selector, and controller classes |
| `public without sharing class Foo` | Only when the class must run elevated (e.g. system-level logging, trigger bypass). Requires a code comment explaining why. |
| `public inherited sharing class Foo` | Framework entry points that should respect the caller's sharing context |
If a class does not have one of these three declarations, **add it before writing anything else**.
## Step 3 — CRUD / FLS Enforcement
Apex code that reads or writes records on behalf of a user must verify object and field access. The platform does **not** enforce FLS or CRUD automatically in Apex.
```apex
// Check before querying a field
if (!Schema.sObjectType.Contact.fields.Email.isAccessible()) {
throw new System.NoAccessException();
}
// Or use WITH USER_MODE in SOQL (API 56.0+)
List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE AccountId = :accId WITH USER_MODE];
// Or use Database.query with AccessLevel
List<Contact> contacts = Database.query('SELECT Id, Email FROM Contact', AccessLevel.USER_MODE);
```
Rule: any Apex method callable from a UI component, REST endpoint, or `@InvocableMethod` **must** enforce CRUD/FLS. Internal service methods called only from trusted contexts may use `with sharing` instead.
## Step 4 — SOQL Injection Prevention
```apex
// ❌ NEVER — concatenates user input into SOQL string
String soql = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';
// ✅ ALWAYS — bind variable
String soql = [SELECT Id FROM Account WHERE Name = :userInput];
// ✅ For dynamic SOQL with user-controlled field names — validate against a whitelist
Set<String> allowedFields = new Set<String>{'Name', 'Industry', 'AnnualRevenue'};
if (!allowedFields.contains(userInput)) {
throw new IllegalArgumentException('Field not permitted: ' + userInput);
}
```
## Step 5 — Modern Apex Idioms
Prefer current language features (API 62.0 / Winter '25+):
| Old pattern | Modern replacement |
|---|---|
| `if (obj != null) { x = obj.Field__c; }` | `x = obj?.Field__c;` |
| `x = (y != null) ? y : defaultVal;` | `x = y ?? defaultVal;` |
| `System.assertEquals(expected, actual)` | `Assert.areEqual(expected, actual)` |
| `System.assert(condition)` | `Assert.isTrue(condition)` |
| `[SELECT ... WHERE ...]` with no sharing context | `[SELECT ... WHERE ... WITH USER_MODE]` |
## Step 6 — PNB Test Coverage Checklist
Every feature must be tested across all three paths. Missing any one of these is a quality failure:
### Positive Path
- Expected input → expected output.
- Assert the exact field values, record counts, or return values — not just that no exception was thrown.
### Negative Path
- Invalid input, null values, empty collections, and error conditions.
- Assert that exceptions are thrown with the correct type and message.
- Assert that no records were mutated when the operation should have failed cleanly.
### Bulk Path
- Insert/update/delete **200–251 records** in a single test transaction.
- Assert that all records processed correctly — no partial failures from governor limits.
- Use `Test.startTest()` / `Test.stopTest()` to isolate governor limit counters for async work.
### Test Class Rules
```apex
@isTest(SeeAllData=false) // Required — no exceptions without a documented reason
private class AccountServiceTest {
@TestSetup
static void makeData() {
// Create all test data here — use a factory if one exists in the project
}
@isTest
static void givenValidInput_whenProcessAccounts_thenFieldsUpdated() {
// Positive path
List<Account> accounts = [SELECT Id FROM Account LIMIT 10];
Test.startTest();
AccountService.processAccounts(accounts);
Test.stopTest();
// Assert meaningful outcomes — not just no exception
List<Account> updated = [SELECT Status__c FROM Account WHERE Id IN :accounts];
Assert.areEqual('Processed', updated[0].Status__c, 'Status should be Processed');
}
}
```
## Step 7 — Trigger Architecture Checklist
- [ ] One trigger per object. If a second trigger exists, consolidate into the handler.
- [ ] Trigger body contains only: context checks, handler invocation, and routing logic.
- [ ] No business logic, SOQL, or DML directly in the trigger body.
- [ ] If a trigger framework (Trigger Actions Framework, ff-apex-common, custom base class) is already in use — extend it. Do not create a parallel pattern.
- [ ] Handler class is `with sharing` unless the trigger requires elevated access.
## Quick Reference — Hardcoded Anti-Patterns Summary
| Pattern | Action |
|---|---|
| SOQL inside `for` loop | Refactor: query before the loop, operate on collections |
| DML inside `for` loop | Refactor: collect mutations, DML once after the loop |
| Class missing sharing declaration | Add `with sharing` (or document why `without sharing`) |
| `escape="false"` on user data (VF) | Remove — auto-escaping enforces XSS prevention |
| Empty `catch` block | Add logging and appropriate re-throw or error handling |
| String-concatenated SOQL with user input | Replace with bind variable or whitelist validation |
| Test with no assertion | Add a meaningful `Assert.*` call |
| `System.assert` / `System.assertEquals` style | Upgrade to `Assert.isTrue` / `Assert.areEqual` |
| Hardcoded record ID (`'001...'`) | Replace with queried or inserted test record ID |
© github · MIT · github/awesome-copilot