Pro Salesforce Development
Battle-tested skill for expert, patterns, salesforce, platform. Includes structured workflows, validation checks, and reusable patterns for development.
Pro Salesforce Development
A comprehensive skill for Salesforce platform development including Lightning Web Components, Apex controllers, SOQL queries, triggers, and integration patterns using the Salesforce DX toolchain.
When to Use This Skill
Choose this skill when:
- Building Lightning Web Components with wire service and imperative Apex
- Writing Apex classes, triggers, and batch jobs
- Designing SOQL queries with relationship traversals and aggregations
- Implementing Salesforce integrations via REST/SOAP APIs
- Setting up CI/CD with Salesforce DX and scratch orgs
Consider alternatives when:
- Building a standalone web app outside Salesforce → use a general web dev skill
- Need CRM customization without code → use Salesforce declarative tools
- Working with MuleSoft integration → use a MuleSoft skill
- Building a Heroku-connected app → use a Heroku skill
Quick Start
# Set up Salesforce DX project sf project generate --name my-project cd my-project sf org login web --set-default-dev-hub sf org create scratch --definition-file config/project-scratch-def.json --alias dev sf project deploy start --source-dir force-app
// force-app/main/default/lwc/accountList/accountList.js import { LightningElement, wire } from 'lwc'; import getAccounts from '@salesforce/apex/AccountController.getAccounts'; export default class AccountList extends LightningElement { @wire(getAccounts, { searchTerm: '$searchTerm' }) accounts; searchTerm = ''; handleSearch(event) { // Debounce search input clearTimeout(this._timer); this._timer = setTimeout(() => { this.searchTerm = event.target.value; }, 300); } get hasAccounts() { return this.accounts?.data?.length > 0; } }
Core Concepts
Development Model
| Layer | Technology | Pattern |
|---|---|---|
| UI | Lightning Web Components | Reactive with @wire decorators |
| Controller | Apex Classes | @AuraEnabled methods with cacheable flag |
| Data Access | SOQL/SOSL | Relationship queries, aggregate functions |
| Automation | Triggers + Flows | Trigger framework pattern, bulkified |
| Integration | REST/Platform Events | Named credentials, async processing |
| Testing | Apex Test Classes | 75% coverage required for deployment |
Apex Controller with Proper Patterns
public with sharing class AccountController { @AuraEnabled(cacheable=true) public static List<Account> getAccounts(String searchTerm) { String term = '%' + String.escapeSingleQuotes(searchTerm) + '%'; return [ SELECT Id, Name, Industry, AnnualRevenue, (SELECT Id, FirstName, LastName FROM Contacts LIMIT 5) FROM Account WHERE Name LIKE :term WITH SECURITY_ENFORCED ORDER BY Name LIMIT 50 ]; } @AuraEnabled public static Account createAccount(Account acc) { // Check CRUD permissions if (!Schema.sObjectType.Account.isCreateable()) { throw new AuraHandledException('Insufficient permissions'); } insert acc; return acc; } }
Trigger Framework Pattern
// Reusable trigger handler pattern public abstract class TriggerHandler { public void run() { if (Trigger.isBefore && Trigger.isInsert) beforeInsert(Trigger.new); if (Trigger.isBefore && Trigger.isUpdate) beforeUpdate(Trigger.new, Trigger.oldMap); if (Trigger.isAfter && Trigger.isInsert) afterInsert(Trigger.new); if (Trigger.isAfter && Trigger.isUpdate) afterUpdate(Trigger.new, Trigger.oldMap); } protected virtual void beforeInsert(List<SObject> newList) {} protected virtual void beforeUpdate(List<SObject> newList, Map<Id,SObject> oldMap) {} protected virtual void afterInsert(List<SObject> newList) {} protected virtual void afterUpdate(List<SObject> newList, Map<Id,SObject> oldMap) {} } // Account-specific handler public class AccountTriggerHandler extends TriggerHandler { protected override void beforeInsert(List<SObject> newList) { for (Account acc : (List<Account>) newList) { if (acc.Industry == null) acc.Industry = 'Other'; } } } // Trigger (one-liner) trigger AccountTrigger on Account (before insert, before update, after insert, after update) { new AccountTriggerHandler().run(); }
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
apiVersion | string | '60.0' | Salesforce API version for metadata |
defaultOrg | string | '' | Default scratch org alias |
testLevel | string | 'RunLocalTests' | Test execution level on deploy |
securityModel | string | 'with sharing' | Default Apex sharing context |
triggerFramework | string | 'handler' | Trigger pattern: handler, domain, or inline |
lwcStylesheet | string | 'scoped' | LWC CSS: scoped or shared stylesheets |
Best Practices
-
Always use
WITH SECURITY_ENFORCEDor manual FLS checks in SOQL — Field-Level Security is not automatically enforced in Apex. Without explicit security checks, users can access fields they shouldn't see through your custom components. -
Bulkify all trigger and batch logic — Triggers can receive up to 200 records at once. Never put SOQL queries or DML statements inside loops. Collect IDs first, query once, then process the results in a map-based lookup pattern.
-
Use @AuraEnabled(cacheable=true) for read operations — Cacheable methods enable Lightning Data Service caching and @wire reactive binding. Only omit cacheable for methods that perform DML or need real-time data on every call.
-
Write test classes that assert behavior, not just coverage — Salesforce requires 75% code coverage, but coverage without assertions catches nothing. Assert expected outcomes, error messages, and edge cases. Use
@TestSetupmethods to create reusable test data. -
Use Named Credentials for external callouts — Never hardcode API endpoints or credentials in Apex code. Named Credentials manage authentication (OAuth, basic auth, certificates) declaratively and support environment-specific configuration.
Common Issues
"Too many SOQL queries: 101" governor limit — SOQL queries inside loops hit the 100-query limit with bulk data. Move queries outside loops, use collections to gather IDs, and perform a single bulk query. Use Limits.getQueries() to monitor usage.
LWC @wire not refreshing after DML — Wire service caches results. After creating or updating records with imperative Apex, call refreshApex(this.wiredResult) to force the wire adapter to refetch. Pass the entire wire result object, not just the data.
Mixed DML operations in test classes — Inserting setup objects (User, Profile) and non-setup objects (Account, Contact) in the same transaction throws an error. Use System.runAs() to isolate setup object DML in a separate execution context within your test methods.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.