
Sf Apex
Implement Salesforce Apex triggers and services using a testable TriggerHandler framework instead of one-off trigger logic.
Overview
sf-apex is an agent skill most often used in Build (also Ship testing, Operate iteration) that implements Salesforce Apex via a TriggerHandler framework and handler subclasses.
Install
npx skills add https://github.com/clientell-ai/salesforce-skills --skill sf-apexWhat is this skill?
- Virtual TriggerHandler base with before/after insert-update-delete-undelete routing
- Test-visible bypass set for unit tests and bulk data loads without firing handlers
- Handler implementation pattern extending base with sharing enforced on concrete classes
- Reference AccountTriggerHandler-style structure for real org customization
- Designed to replace monolithic triggers with one-handler-per-object discipline
Adoption & trust: 1 installs on skills.sh; 7 GitHub stars; 3/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
What problem does it solve?
Your Salesforce triggers are spaghetti, hard to test, and you cannot selectively disable logic during bulk loads.
Who is it for?
Indie consultants and builders adding or refactoring Apex triggers on standard or custom objects in shared orgs.
Skip if: Flows-only automation with no Apex, or greenfield teams that need full security, limits, and deployment checklist docs rather than trigger patterns alone.
When should I use this skill?
Authoring or refactoring Salesforce Apex triggers, implementing handler subclasses, or needing test bypass for triggers.
What do I get? / Deliverables
Triggers delegate to a structured handler hierarchy with bypass support so features stay modular and tests stay deterministic.
- TriggerHandler base and object-specific handler classes
- Trigger thin delegator file per object
- Test patterns using bypass/clearBypass helpers
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Apex handlers and DML patterns are authored during product build on Salesforce; canonical shelf is Build backend even though triggers affect ship-time regression testing. Server-side Salesforce logic—triggers, handlers, sharing—maps to backend subphase for platform extensions and CRM-integrated SaaS.
Where it fits
Scaffold AccountTriggerHandler before adding validation rules on insert.
Use TriggerHandler.bypass in test classes to load fixtures without side effects.
Extend afterUpdate hooks when production sync requirements change.
How it compares
Platform Apex architecture patterns—not generic PHP/Java REST API skills or Salesforce Flow authoring.
Common Questions / FAQ
Who is sf-apex for?
Solo builders and small teams writing Salesforce Apex who want a proven trigger handler skeleton instead of copying fragmented Stack Overflow snippets.
When should I use sf-apex?
Use it in Build when scaffolding triggers; in Ship when structuring tests with handler bypass; in Operate when iterating handlers without breaking bulk data jobs.
Is sf-apex safe to install?
Check the Security Audits panel on this Prism page; Apex in production orgs still demands your own review of sharing, CRUD/FLS, and packaged IP policies.
SKILL.md
READMESKILL.md - Sf Apex
# Apex Design Patterns Reference ## Trigger Handler Framework ### Base Handler ```apex public virtual class TriggerHandler { @TestVisible private static Set<String> bypassedHandlers = new Set<String>(); public void run() { if (bypassedHandlers.contains(getHandlerName())) return; if (Trigger.isBefore) { if (Trigger.isInsert) beforeInsert(); if (Trigger.isUpdate) beforeUpdate(); if (Trigger.isDelete) beforeDelete(); } else if (Trigger.isAfter) { if (Trigger.isInsert) afterInsert(); if (Trigger.isUpdate) afterUpdate(); if (Trigger.isDelete) afterDelete(); if (Trigger.isUndelete) afterUndelete(); } } public static void bypass(String handlerName) { bypassedHandlers.add(handlerName); } public static void clearBypass(String handlerName) { bypassedHandlers.remove(handlerName); } private String getHandlerName() { return String.valueOf(this).split(':')[0]; } protected virtual void beforeInsert() {} protected virtual void beforeUpdate() {} protected virtual void beforeDelete() {} protected virtual void afterInsert() {} protected virtual void afterUpdate() {} protected virtual void afterDelete() {} protected virtual void afterUndelete() {} } ``` ### Handler Implementation ```apex public with sharing class AccountTriggerHandler extends TriggerHandler { private List<Account> newRecords; private Map<Id, Account> oldMap; public AccountTriggerHandler() { this.newRecords = (List<Account>) Trigger.new; this.oldMap = (Map<Id, Account>) Trigger.oldMap; } protected override void beforeInsert() { AccountService.setDefaults(newRecords); } protected override void afterUpdate() { List<Account> nameChanged = new List<Account>(); for (Account acc : newRecords) { if (acc.Name != oldMap.get(acc.Id).Name) { nameChanged.add(acc); } } if (!nameChanged.isEmpty()) { AccountService.syncContactAddresses(nameChanged); } } } ``` ## Service Layer Pattern ```apex public with sharing class AccountService { public static void setDefaults(List<Account> accounts) { for (Account acc : accounts) { if (acc.Industry == null) { acc.Industry = 'Other'; } } } public static void syncContactAddresses(List<Account> accounts) { Set<Id> accountIds = new Map<Id, Account>(accounts).keySet(); List<Contact> contacts = [ SELECT Id, AccountId, MailingStreet FROM Contact WHERE AccountId IN :accountIds WITH USER_MODE ]; // Update logic... } } ``` ## Selector Pattern ```apex public with sharing class AccountSelector { public static List<Account> getByIds(Set<Id> ids) { return [ SELECT Id, Name, Industry, BillingStreet FROM Account WHERE Id IN :ids WITH USER_MODE ]; } public static List<Account> getByIndustry(String industry) { return [ SELECT Id, Name, Industry FROM Account WHERE Industry = :industry WITH USER_MODE LIMIT 200 ]; } public static List<Account> getWithContacts(Set<Id> ids) { return [ SELECT Id, Name, (SELECT Id, FirstName, LastName, Email FROM Contacts) FROM Account WHERE Id IN :ids WITH USER_MODE ]; } } ``` ## Batch Apex Pattern ```apex public with sharing class AccountCleanupBatch implements Database.Batchable<SObject>, Database.Stateful { private Integer processedCount = 0; private List<String> errors = new List<String>(); public Database.QueryLocator start(Database.BatchableContext bc) { retu