Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Sf Apex Development

ASecurity

Writes and reviews production Salesforce Apex — classes, interfaces, inheritance, sharing keywords (with/without/inherited sharing), user mode versus system mode (as user, as system, WITH USER_MODE, AccessLevel), one-trigger-per-object handler frameworks, recursion guards, bulkification, collection and map idioms, Database.insert partial success and SaveResult handling, Savepoint and rollback, custom exceptions, Database.Stateful, JSON serialisation, Schema describe caching, custom settings a...

2 stars
0 votes
0 copies
0 views
Added 9/19/2026
developmentgobashnodetestingdebugginggitapidatabasesecurity

Works with

api

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add grzmol/vibe-force --skill sf-apex-development --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sf Apex Development?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Sf Apex Development
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/grzmol-sf-apex-development/badge)](https://www.skillsdirectory.com/skills/grzmol-sf-apex-development)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: sf-apex-development
description: Writes and reviews production Salesforce Apex — classes, interfaces, inheritance, sharing keywords (with/without/inherited sharing), user mode versus system mode (as user, as system, WITH USER_MODE, AccessLevel), one-trigger-per-object handler frameworks, recursion guards, bulkification, collection and map idioms, Database.insert partial success and SaveResult handling, Savepoint and rollback, custom exceptions, Database.Stateful, JSON serialisation, Schema describe caching, custom settings and custom metadata access, Apex Enterprise Patterns (Selector, Domain, Service, Unit of Work), @AuraEnabled(cacheable=true) contracts for Lightning web components, and @RestResource services. Use this skill whenever work touches force-app/**/classes/*.cls or force-app/**/triggers/*.trigger, when an Apex class or trigger must be created, refactored, bulkified or security-hardened, when a reviewer asks why a class needs an explicit sharing declaration, when Apex must be exposed to LWC or REST, or before running vf-check analyzer or vf-check apex.
---

# Apex Development

Target API version is `apiVersion` in `config/vibe-force.defaults.json` (`67.0`). API 67.0 changed
how Apex enforces security by default; every rule below assumes it.

| Behaviour | API 66.0 and earlier | API 67.0 and later |
| --- | --- | --- |
| Class with no sharing declaration | Resolved from inheritance chain / entry point; often `without sharing` | Runs `with sharing` |
| SOQL, SOSL, DML default access mode | System mode (FLS and object perms bypassed) | **User mode** (FLS and object perms enforced) |
| Trigger body database operations | System mode | User mode, which overrides the trigger's implicit `without sharing` |
| `WITH SECURITY_ENFORCED` in an Apex SOQL `SELECT` | allowed, weaker than user mode | **rejected — will not compile**; use `WITH USER_MODE` |

## When to use

- Creating or editing anything under `force-app/**/classes/` or `force-app/**/triggers/`.
- Converting ad-hoc trigger logic into a handler framework, or being asked to add a second trigger
  to an object (never do it — see pattern 3).
- Choosing between `with sharing`, `without sharing`, `inherited sharing`, and `WITH SYSTEM_MODE`.
- Exposing Apex to Lightning web components (`@AuraEnabled`) or external callers (`@RestResource`).
- Reviewing for bulkification, partial-success DML, describe-call caching, or exception design.

Adjacent skills: `sf-apex-testing` (tests, mocks, coverage), `sf-async-apex-patterns` (Queueable,
Batch, Schedulable, future, finalizers), `sf-governor-limits` (limit numbers and triage),
`sf-soql-sosl-optimization` (selectivity, indexes), `sf-security-model` (CRUD/FLS, permission sets,
`stripInaccessible`), `sf-debugging-logs` (log levels), `sf-integration-patterns` (callouts).

## Decision table

| Question | Answer | Enforced by |
| --- | --- | --- |
| Sharing declaration on a new class? | `with sharing` unless the class is a deliberate privilege elevation | `vf-check analyzer` (PMD `ApexSharingViolations`) |
| Service reused from elevated and non-elevated callers? | `inherited sharing` | review |
| Query must ignore FLS? | `WITH SYSTEM_MODE` plus a comment stating why | `vf-check analyzer` |
| DML must ignore FLS? | `insert as system` / `Database.insert(recs, AccessLevel.SYSTEM_MODE)` | review |
| Triggers per object? | Exactly one, all events in one file, zero logic inside | `vf-check analyzer` |
| Partial success acceptable? | `Database.insert(list, false)` and iterate `SaveResult` | review |
| All-or-nothing required? | `insert list;` or `Database.insert(list, true)` | review |
| Method called from LWC and read-only? | `@AuraEnabled(cacheable=true)` | `vf-check lint` (LWC side) |
| Describe call inside a loop? | Hoist into a `static final Map` | `vf-check analyzer` |
| New Apex class without a matching `*Test.cls`? | Blocked when `gates.requireTestForApexClass` is true | `vf-check apex` |

## Core patterns

### 1. Explicit sharing plus explicit access mode

The sharing keyword governs record visibility; the access mode governs object and field
permissions. They are independent — declare both.

```apex
public with sharing class OpportunityRiskService {
    // User mode is the API 67.0 default, but state it so intent survives a version bump.
    public static List<Opportunity> loadAtRisk(Set<Id> accountIds) {
        return [
            SELECT Id, Name, Amount, CloseDate, AccountId
            FROM Opportunity
            WHERE AccountId IN :accountIds
              AND StageName NOT IN ('Closed Won', 'Closed Lost')
            WITH USER_MODE
        ];
    }
}
```

Elevation is a separate, narrow class — never a `without sharing` flag on a general service:

```apex
/** Community users must read the parent account name of their own cases. Name only. */
public without sharing class AccountNameElevation {
    public static List<Account> namesFor(Set<Id> accountIds) {
        return [SELECT Id, Name FROM Account WHERE Id IN :accountIds WITH SYSTEM_MODE];
    }
}
```

`inherited sharing` resolves at run time and runs `with sharing` at every entry point (Aura
controller, `@AuraEnabled` method called from LWC, Visualforce controller, Apex REST service,
asynchronous Apex). It degrades to `without sharing` only when called from an established
`without sharing` context. Triggers cannot carry a sharing declaration — they always run
`without sharing` — so keep the trigger body empty and put logic in a declared handler class. Full
matrix in [references/apex-language-reference.md](references/apex-language-reference.md).

### 2. Bulkified, map-driven logic

One query per object, one DML per object, zero queries or DML inside loops.

```apex
public with sharing class CaseEscalationService {
    public static void escalate(List<Case> cases) {
        Set<Id> accountIds = new Set<Id>();
        for (Case c : cases) {
            if (c.AccountId != null) {
                accountIds.add(c.AccountId);
            }
        }
        if (accountIds.isEmpty()) {
            return;
        }

        Map<Id, Account> accounts = new Map<Id, Account>([
            SELECT Id, OwnerId, Tier__c FROM Account WHERE Id IN :accountIds WITH USER_MODE
        ]);

        List<Case> toUpdate = new List<Case>();
        for (Case c : cases) {
            Account a = accounts.get(c.AccountId);
            if (a != null && a.Tier__c == 'Platinum' && c.Priority != 'High') {
                toUpdate.add(new Case(Id = c.Id, Priority = 'High', OwnerId = a.OwnerId));
            }
        }
        if (!toUpdate.isEmpty()) {
            update as user toUpdate;
        }
    }
}
```

| Need | Idiom |
| --- | --- |
| Ids from a trigger collection | `Trigger.newMap.keySet()`, or bind `Trigger.new` directly in `IN :Trigger.new` (Apex converts records to Ids) |
| Group children by parent | `Map<Id, List<Child__c>>` built in one pass over one query |
| Detect a changed field | `Trigger.oldMap.get(rec.Id).Field__c != rec.Field__c` |
| Query result as a keyed map | `new Map<Id, Account>([SELECT ...])` |
| De-duplicate external keys | `Set<String>` then a single `WHERE ExternalId__c IN :keys` |
| Constant lookups | `static final Map<String, X>` initialised in a static block |

### 3. One trigger per object, logic in a handler

The order in which multiple triggers on the same object fire is **not guaranteed**, so a second
trigger on an object is a defect, not a style preference.

```apex
trigger CaseTrigger on Case (
    before insert, before update, before delete,
    after insert, after update, after delete, after undelete
) {
    new CaseTriggerHandler().run();
}
```

The runnable `TriggerHandler` base class (dispatch by `Trigger.operationType`, per-handler
recursion guard, static bypass API, max-loop-count support) and its tests are in
[references/trigger-framework.md](references/trigger-framework.md). Minimal Id-set guard for a
handler that re-enters through its own DML:

```apex
@TestVisible
private static Set<Id> processed = new Set<Id>();

List<Contact> fresh = new List<Contact>();
for (Contact c : contacts) {
    if (processed.add(c.Id)) {   // Set.add returns false when already present
        fresh.add(c);
    }
}
```

Static state is **not** reset between the retry attempts of a partial-success bulk DML call, and is
**not** reverted by `Database.rollback`. Design guards to tolerate re-entry, and prefer an
`operationType`-keyed guard over a global boolean.

### 4. DML options, partial success, and rollback

```apex
Database.SaveResult[] results = Database.insert(leads, false, AccessLevel.USER_MODE);
for (Integer i = 0; i < results.size(); i++) {
    if (results[i].isSuccess()) { continue; }
    for (Database.Error err : results[i].getErrors()) {
        // getFields() is populated for FLS failures when running in user mode
        errorsByRow.put(i, err.getStatusCode() + ': ' + err.getMessage()
            + ' fields=' + String.join(err.getFields(), ','));
    }
}
```

| Operation | Result class |
| --- | --- |
| `insert`, `update` | `Database.SaveResult` |
| `upsert` | `Database.UpsertResult` |
| `merge` | `Database.MergeResult` |
| `delete` | `Database.DeleteResult` |
| `undelete` | `Database.UndeleteResult` |
| `convertLead` | `Database.LeadConvertResult` |
| `emptyRecycleBin` | `Database.EmptyRecycleBinResult` |

DML statements and `Database.*` methods with `allOrNone = true` roll the whole operation back.
With `allOrNone = false` the platform retries up to three times, resetting governor limits between
attempts and re-firing triggers on the shrinking subset; a third failure aborts with
`Too many batch retries in the presence of Apex triggers and partial failures.`

Savepoints cost one DML statement each, do not cost DML rows, cannot cross trigger invocations, and
must be released before a callout:

```apex
Savepoint sp = Database.setSavepoint();
try {
    insert as user orders;
    insert as user orderItems;
} catch (DmlException e) {
    Database.rollback(sp);
    Database.releaseSavepoint(sp);   // required before any callout in this transaction
    throw new OrderException('Order creation failed', e);
}
```

Failing to release produces `All active Savepoints must be released before making callouts.`;
pending uncommitted DML produces `You have uncommitted work pending. Please commit or rollback
before calling out.`

### 5. Custom exceptions that carry data, not strings

Any class whose name ends in `Exception` and extends `Exception` is a custom exception. Never put
personal data in the message — subclass with typed properties instead.

```apex
public with sharing class OrderException extends Exception {
    public List<Id> failedRecordIds { get; private set; }

    public OrderException(String message, List<Id> failedRecordIds) {
        this(message);
        this.failedRecordIds = failedRecordIds;
    }
}
```

Trigger-level validation uses `addError` rather than a thrown exception, so the platform can build
a complete error list and support partial saves. An unhandled exception in a trigger marks **every**
record in scope as failed and stops processing.

```apex
for (Case c : Trigger.new) {
    if (String.isBlank(c.Subject)) {
        c.Subject.addError(Label.Case_Subject_Required);
    }
}
```

### 6. Describe calls, custom settings, custom metadata

`Schema.getGlobalDescribe()` builds the whole org map — never call it in a loop, and never call it
at all when a token literal works.

```apex
private static final Map<String, Schema.SObjectField> CASE_FIELDS =
    Schema.SObjectType.Case.fields.getMap();
```

| Configuration store | Apex access | SOQL cost |
| --- | --- | --- |
| Hierarchy custom setting | `MySetting__c.getInstance()`, `getInstance(profileOrUserId)`, `getOrgDefaults()` | none — application cache |
| List custom setting | `MySetting__c.getValues('name')`, `getAll()` | none — application cache |
| Custom metadata type | `[SELECT ... FROM My_Type__mdt]`, `My_Type__mdt.getInstance('DevName')` | queries on `__mdt` do not count against the transaction SOQL limit |

Custom metadata is the right home for per-environment tuning because its queries are unmetered.
Never store secrets in custom settings — outside a managed package they are readable by every
profile, including the guest user.

### 7. Enterprise patterns: Selector, Domain, Service, Unit of Work

| Layer | Owns | Never does |
| --- | --- | --- |
| Selector | SOQL for one sObject, field lists, access mode | business rules, DML |
| Domain | per-record validation and defaulting for one sObject | cross-object orchestration |
| Service | a named business transaction across objects | direct SOQL, direct DML ordering |
| Unit of Work | registration and ordered commit of dirty records | business rules |

```apex
public with sharing class OpportunityService {
    public static void closeWon(Set<Id> opportunityIds) {
        UnitOfWork uow = new UnitOfWork(
            new List<SObjectType>{ Opportunity.SObjectType, Task.SObjectType }
        );
        for (Opportunity o : new OpportunitySelector().selectOpenByIds(opportunityIds)) {
            uow.registerDirty(new Opportunity(Id = o.Id, StageName = 'Closed Won'));
            uow.registerNew(new Task(WhatId = o.Id, Subject = 'Kick off delivery'));
        }
        uow.commitWork();
    }
}
```

Full runnable `SObjectSelector`, `Domain`, `Service`, and `UnitOfWork` implementations (no external
library required) are in [references/enterprise-patterns.md](references/enterprise-patterns.md).

### 8. Apex exposed to LWC and REST

```apex
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> searchAccounts(String term) {
        String like = '%' + String.escapeSingleQuotes(term) + '%';
        return [
            SELECT Id, Name, Industry, AnnualRevenue
            FROM Account
            WHERE Name LIKE :like
            WITH USER_MODE
            ORDER BY Name
            LIMIT 50
        ];
    }

    @AuraEnabled
    public static Id createAccount(String name) {
        try {
            Account a = new Account(Name = name);
            insert as user a;
            return a.Id;
        } catch (DmlException e) {
            throw new AuraHandledException(e.getDmlMessage(0));   // never leak a stack trace
        }
    }
}
```

`cacheable=true` is required for `@wire` and forbids DML in the method. Method overloads are not
allowed on `@AuraEnabled` methods from API 55.0 onward. `@AuraEnabled(cacheable=true scope='global')`
(API 55.0+) promotes results to the global cache.

```apex
@RestResource(urlMapping='/v1/accounts/*')
global with sharing class AccountRest {
    @HttpGet
    global static Account getAccount() {
        String id = RestContext.request.requestURI.substringAfterLast('/');
        return [SELECT Id, Name, Industry FROM Account WHERE Id = :id WITH USER_MODE];
    }
}
```

`@RestResource` requires a `global` class, maps relative to
`https://<instance>/services/apexrest/`, is case sensitive, and resolves exact matches before the
longest wildcard match. Outbound callouts belong in `sf-integration-patterns`.

## Anti-patterns

| Anti-pattern | Failing code | Fix |
| --- | --- | --- |
| SOQL in a loop | `for (Case c : Trigger.new) { Account a = [SELECT ... WHERE Id = :c.AccountId]; }` | one `WHERE Id IN :accountIds` query into a `Map<Id, Account>` |
| DML in a loop | `for (String n : names) { insert new Account(Name = n); }` | accumulate into a `List`, then one `insert` |
| Trigger assuming one record | `User u = [SELECT Id FROM User WHERE Mileage__c = :Trigger.new[0].Id];` | bind the collection: `WHERE Mileage__c IN :Trigger.newMap.keySet()` |
| Two triggers per object | `CaseTrigger` plus `CaseAuditTrigger` | one trigger, two handler methods, explicit order |
| Boolean recursion flag that never resets | `if (done) return; done = true;` | Id-set guard keyed by operation, `@TestVisible` so tests can reset |
| `without sharing` on a shared service | `public without sharing class AccountService` | `with sharing` service plus a tiny `without sharing` helper for the one elevated read |
| Silent swallow | `try { ... } catch (Exception e) {}` | rethrow a typed custom exception or `addError` |
| `Database.insert(list, false)` with results discarded | failures invisible | iterate `SaveResult`, surface `getErrors()` |
| Describe in a loop | `Schema.getGlobalDescribe().get(name)` per iteration | hoist into a `static final Map` |
| Hard-coded Ids | `if (p.Id == '00e1x...')` | custom metadata type, or query by name |
| `System.assertEquals` used as production validation | assertion in non-test code | `addError`, or throw a custom exception |
| `@AuraEnabled(cacheable=true)` performing DML | runtime error at call time | drop `cacheable`, or split read and write methods |

Rows 1, 2, 8 and 10 are detected by `vf-check analyzer` (`AvoidSoqlInLoops`,
`AvoidDmlStatementsInLoops`, `OperationWithLimitsInLoop`, `AvoidHardcodedId`); the rest are review.

## Verification

```bash
# Static gate (prettier + eslint + code-analyzer) then the full local gate (adds LWC Jest)
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" static --changed
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" local --changed

# Org-side Apex tests + coverage gates (apexOrgCoverageMin 85 / apexClassCoverageMin 75)
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex --target-org vf-dev

# Compile-only proof that the classes deploy
sf project deploy validate --source-dir force-app/main/default/classes \
  --test-level RunSpecifiedTests --tests CaseEscalationServiceTest --target-org vf-dev

# Behaviour probe against a live org
sf apex run --file scripts/apex/probe-escalation.apex --target-org vf-dev
```

## References

| Reference file | Contents |
| --- | --- |
| [references/trigger-framework.md](references/trigger-framework.md) | runnable `TriggerHandler` base class, dispatcher, bypass API, recursion control, handler tests |
| [references/enterprise-patterns.md](references/enterprise-patterns.md) | Selector, Domain, Service, Unit of Work with complete code and a layering checklist |
| [references/order-of-execution.md](references/order-of-execution.md) | 20-step save order, recursive-save skips, workflow re-fire, roll-up cascades, operations that skip triggers |
| [references/apex-language-reference.md](references/apex-language-reference.md) | annotations, access modifiers, sharing and user-mode matrix, trigger context variables, System classes |

| Official source | URL |
| --- | --- |
| Apex Developer Guide (PDF, Winter '27 / API 68.0) | https://resources.docs.salesforce.com/264/latest/en-us/sfdc/pdf/salesforce_apex_developer_guide.pdf |
| Apex Reference Guide (PDF, Summer '26 / API 67.0) | https://resources.docs.salesforce.com/262/latest/en-us/sfdc/pdf/salesforce_apex_reference_guide.pdf |
| Sharing keywords | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_keywords_sharing.htm |
| Access mode for database operations | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_enforce_usermode.htm |
| Triggers and order of execution | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm |
| Trigger context variables | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_context_variables.htm |
| Bulk DML exception handling | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dml_bulk_exceptions.htm |
| Transaction control and savepoints | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_transaction.htm |
| Custom exceptions | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_exception_custom.htm |
| `@AuraEnabled` | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_AuraEnabled.htm |
| `@RestResource` | https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_rest_resource.htm |
| Apex Enterprise Patterns (Trailhead) | https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_sl |
| fflib-apex-common reference implementation | https://github.com/apex-enterprise-patterns/fflib-apex-common |

Attribution

grzmolgrzmol
View sourceMore from grzmol →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →