Apply these checks to every LWC, Aura component, and Visualforce page you write or review.
Choose the right data access pattern before writing JavaScript controller code:
| Use case | Pattern | Why |
|---|---|---|
| Read a single record reactively (follows navigation) | @wire(getRecord, { recordId, fields }) |
Lightning Data Service — cached, reactive |
| Standard CRUD form for a single object | <lightning-record-form> or <lightning-record-edit-form> |
Built-in FLS, CRUD, and accessibility |
| Complex server query or filtered list | @wire(apexMethodName, { param }) on a cacheable=true method |
Allows caching; wire re-fires on param change |
| User-triggered action, DML, or non-cacheable server call | Imperative apexMethodName(params).then(...).catch(...) |
Required for DML — wired methods cannot be @AuraEnabled without cacheable=true |
| Cross-component communication (no shared parent) | Lightning Message Service (LMS) | Decoupled, works across DOM boundaries |
| Multi-object graph relationships | GraphQL @wire(gql, { query, variables }) |
Single round-trip for complex related data |
| Rule | Enforcement |
|---|---|
No raw user data in innerHTML |
Use {expression} binding in the template — the framework auto-escapes. Never use this.template.querySelector('.el').innerHTML = userValue |
Apex @AuraEnabled methods enforce CRUD/FLS |
Use WITH USER_MODE in SOQL or explicit Schema.sObjectType checks |
| No hardcoded org-specific IDs in component JavaScript | Query or pass as a prop — never embed record IDs in source |
@api properties from parent: validate before use |
A parent can pass anything — validate type and range before using as a query parameter |
color: #FF3366 → use color: var(--slds-c-button-brand-color-background) or a semantic SLDS token.!important — compose with custom CSS properties.<lightning-*> base components wherever they exist: lightning-button, lightning-input, lightning-datatable, lightning-card, etc.Every LWC component must pass all of these before it is considered done:
<label> or aria-label — never use placeholder as the only labelalternative-text or aria-label describing the actionaria-* attributesaria-describedby
| Direction | Mechanism |
|---|---|
| Parent → Child | @api property or calling a @api method |
| Child → Parent | CustomEvent — this.dispatchEvent(new CustomEvent('eventname', { detail: data })) |
| Sibling / unrelated components | Lightning Message Service (LMS) |
| Never use | document.querySelector, window.*, or Pub/Sub libraries |
For Flow screen components:
bubbles: true and composed: true.@api value for two-way binding with the Flow variable.connectedCallback: it runs on every DOM attach — avoid DML, heavy computation, or rendering state mutations here.renderedCallback: always use a boolean guard to prevent infinite render loops.renderedCallback causes a re-render — use it only when necessary and guarded.Every component that handles user interaction or retrieves Apex data must have a Jest test:
// Minimum test coverage expectations
it('renders the component with correct title', async () => { ... });
it('calls apex method and displays results', async () => { ... }); // Wire mock
it('dispatches event when button is clicked', async () => { ... });
it('shows error state when apex call fails', async () => { ... }); // Error path
Use @salesforce/sfdx-lwc-jest mocking utilities:
wire adapter mocking: setImmediate + emit({ data, error })
jest.mock('@salesforce/apex/MyClass.myMethod', ...)
force:appPage, using Aura-specific events in a legacy managed package).@AuraEnabled controller methods must declare with sharing and enforce CRUD/FLS — Aura does not enforce them automatically.{!v.something} with unescaped user data in <div> unbound helpers — use <ui:outputText value="{!v.text}" /> or <c:something> to escape.<!-- ❌ NEVER — renders raw user input as HTML -->
<apex:outputText value="{!userInput}" escape="false" />
<!-- ✅ ALWAYS — auto-escaping on -->
<apex:outputText value="{!userInput}" />
<!-- Default escape="true" — platform HTML-encodes the output -->
Rule: escape="false" is never acceptable for user-controlled data. If rich text must be rendered, sanitise server-side with a whitelist before output.
Use <apex:form> for all postback actions — the platform injects a CSRF token automatically into the form. Do not use raw <form method="POST"> HTML elements, which bypass CSRF protection.
// ❌ NEVER
String soql = 'SELECT Id FROM Account WHERE Name = \'' + ApexPages.currentPage().getParameters().get('name') + '\'';
List<Account> results = Database.query(soql);
// ✅ ALWAYS — bind variable
String nameParam = ApexPages.currentPage().getParameters().get('name');
List<Account> results = [SELECT Id FROM Account WHERE Name = :nameParam];
transient
readonly="true" is set on <apex:page> for read-only pages to skip view-state serialisation// Before reading a field
if (!Schema.sObjectType.Account.fields.Revenue__c.isAccessible()) {
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'You do not have access to this field.'));
return null;
}
// Before performing DML
if (!Schema.sObjectType.Account.isDeletable()) {
throw new System.NoAccessException();
}
Standard controllers enforce FLS for bound fields automatically. Custom controllers do not — FLS must be enforced manually.
| Anti-pattern | Technology | Risk | Fix |
|---|---|---|---|
innerHTML with user data |
LWC | XSS | Use template bindings {expression} |
| Hardcoded hex colours | LWC/Aura | Dark-mode / SLDS 2 break | Use SLDS CSS custom properties |
Missing aria-label on icon buttons |
LWC/Aura/VF | Accessibility failure | Add alternative-text or aria-label |
No guard in renderedCallback |
LWC | Infinite rerender loop | Add hasRendered boolean guard |
| Application event for parent-child | Aura | Unnecessary broadcast scope | Use component event instead |
escape="false" on user data |
Visualforce | XSS | Remove — use default escaping |
Raw <form> postback |
Visualforce | CSRF vulnerability | Use <apex:form> |
No with sharing on custom controller |
VF / Apex | Data exposure | Add with sharing declaration |
| FLS not checked in custom controller | VF / Apex | Privilege escalation | Add Schema.sObjectType checks |
| SOQL concatenated with URL param | VF / Apex | SOQL injection | Use bind variables |