
Sf Lwc
Generate secure, bulk-safe Apex controllers with cacheable and mutating @AuraEnabled methods for Lightning Web Components.
Overview
sf-lwc is an agent skill for the Build phase that scaffolds secure Apex controllers for Lightning Web Components with cacheable wire methods and mutation APIs.
Install
npx skills add https://github.com/jaganpro/sf-skills --skill sf-lwcWhat is this skill?
- Apex controller template with separate cacheable (@wire) and non-cacheable mutation method sections
- WITH SECURITY_ENFORCED queries and sharing-aware class declaration (with sharing)
- Parameterized list fetch with optional parentId filter, searchTerm escaping, and limitSize cap defaulting to 50
- Try/catch error handling patterns suited to LWC imperative and wire callers
- Replaceable LwcController placeholder for consistent naming across generated controllers
- Default list fetch cap of 50 records when limitSize is omitted
- Template splits cacheable wire methods and non-cacheable mutation methods into distinct sections
Adoption & trust: 1.4k installs on skills.sh; 418 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are building an LWC but lack a consistent, secure Apex controller pattern for @wire reads and imperative updates.
Who is it for?
Developers on Salesforce orgs who want agent-generated controllers that follow LWC @AuraEnabled conventions and security enforcement.
Skip if: Greenfield apps outside Salesforce, or teams that only need Visualforce or Experience Cloud theming without Apex APIs.
When should I use this skill?
User is building or refactoring Lightning Web Components and needs an Apex controller with @AuraEnabled cacheable and mutation methods.
What do I get? / Deliverables
You get a sharing-enforced Apex controller skeleton with cacheable getters and mutation methods ready to rename and wire into your LWC.
- Renamed Apex controller class with wire-ready and mutation @AuraEnabled methods
- Documented error-handling structure for LWC callers
Recommended Skills
Journey fit
LWC plus Apex controller work is product implementation on the Salesforce UI layer, which belongs on the Build shelf. The skill targets LWC consumption patterns (@wire, mutations) and presentation-oriented Apex APIs, not org-wide DevOps or release trains.
How it compares
Use this LWC-focused Apex template instead of generic Apex snippets that omit cacheable versus non-cacheable splits and WITH SECURITY_ENFORCED.
Common Questions / FAQ
Who is sf-lwc for?
Solo builders and consultants implementing Salesforce LWC features who need repeatable Apex controller structure with security and wire-friendly methods.
When should I use sf-lwc?
Use it in the Build phase while creating or extending Lightning Web Components that need server-side queries and DML with proper @AuraEnabled contracts.
Is sf-lwc safe to install?
Check the Security Audits panel on this Prism page before installing; generated Apex still requires your org’s review, tests, and deployment practices.
SKILL.md
READMESKILL.md - Sf Lwc
/** * APEX CONTROLLER TEMPLATE FOR LWC * * This template demonstrates @AuraEnabled methods for LWC with: * - Cacheable methods for wire service * - Non-cacheable methods for data mutations * - Proper error handling * - Security enforcement * - Bulk-safe patterns * * Replace: LwcController → YourControllerName */ public with sharing class LwcController { // ═══════════════════════════════════════════════════════════════════════ // CACHEABLE METHODS (For @wire service) // ═══════════════════════════════════════════════════════════════════════ /** * Get records for display in LWC * Use with @wire decorator for automatic caching and refresh * * @param parentId - Parent record ID (optional filter) * @param searchTerm - Search filter (optional) * @param limitSize - Max records to return * @return List of Account records */ @AuraEnabled(cacheable=true) public static List<Account> getAccounts(Id parentId, String searchTerm, Integer limitSize) { try { Integer recordLimit = limitSize != null ? limitSize : 50; String searchKey = String.isNotBlank(searchTerm) ? '%' + String.escapeSingleQuotes(searchTerm) + '%' : '%'; return [ SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate FROM Account WHERE Name LIKE :searchKey WITH SECURITY_ENFORCED ORDER BY Name LIMIT :recordLimit ]; } catch (Exception e) { throw new AuraHandledException(e.getMessage()); } } /** * Get single record by ID */ @AuraEnabled(cacheable=true) public static Account getAccountById(Id accountId) { try { List<Account> accounts = [ SELECT Id, Name, Industry, AnnualRevenue, Phone, BillingAddress, (SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 5) FROM Account WHERE Id = :accountId WITH SECURITY_ENFORCED LIMIT 1 ]; if (accounts.isEmpty()) { throw new AuraHandledException('Account not found'); } return accounts[0]; } catch (Exception e) { throw new AuraHandledException(e.getMessage()); } } /** * Get picklist values for a field */ @AuraEnabled(cacheable=true) public static List<PicklistOption> getIndustryOptions() { List<PicklistOption> options = new List<PicklistOption>(); Schema.DescribeFieldResult fieldResult = Account.Industry.getDescribe(); List<Schema.PicklistEntry> entries = fieldResult.getPicklistValues(); for (Schema.PicklistEntry entry : entries) { if (entry.isActive()) { options.add(new PicklistOption(entry.getLabel(), entry.getValue())); } } return options; } // ═══════════════════════════════════════════════════════════════════════ // NON-CACHEABLE METHODS (For data mutations) // ═══════════════════════════════════════════════════════════════════════ /** * Create a new account * * @param accountData - JSON string of account fields * @return Created Account with Id */ @AuraEnabled public static Account createAccount(String accountData) { try { // Parse JSON to Map Map<String, Object> fieldMap = (Map<String, Object>) JSON.deserializeUntyped(accountData); // Create Account record Account newAccount = new Account(); newAccount.Name = (String) fieldMap.get('Name'); newAccount.Industry = (String) fieldMap.get('Industry'); newAccount.Phone = (String) fieldMap.get('Phone'); // Validate required fields if (String.isBlank(newAccount.Name)) {