Skip to content

General

Standards that apply to every language. Each standard is stated once here, by topic, and the language pages link to it; a standard that belongs to one language stays on that language's page. Naming is the exception: the naming standards of every language are here, under one heading for each language.

Accessibility

Accessibility standards that apply to every kind of markup: Visualforce, HTML, Lightning Web Components, and Aura. The baseline, WCAG 2.2 Level AA, how it is enforced, and the laws that require it are on the Accessibility principle page. The standards that belong to one kind of markup, such as the tables, forms, and media of HTML and Visualforce, stay on its page.

Text Alternatives

A screen reader announces an image by its alternative text, and a button or a link by its name. Without them, the user hears a file name, or only "button" or "link", and cannot tell what the image shows or what the control does. Every image that carries information has a text alternative (WCAG 1.1.1), and every control has a name (WCAG 4.1.2). Both are Level A.

Images

  • Every image has an alternative text attribute: alt on an img or an apex:image, alternative-text on a lightning-carousel-image, and alternativeText on a lightning:carouselImage, the attribute that the base components give an image for WCAG 1.1.1 (Base Components Accessibility). Its text says what the image shows (technique H37). An image with no such attribute blocks the pull request (severity 2). Enforced by ImageWithoutAltVf and ImageWithoutAltHtmlLwcAura.
  • A decorative image, one that carries no information, has an empty value, which tells assistive technology to ignore it (technique H67), and only a decorative image should have an empty value. An image with an empty or blank value is reported at severity 4, so that the reviewer may confirm that it is decorative, with the W3C's decision tree (An alt Decision Tree). Enforced by ImageWithEmptyAltVf and ImageWithEmptyAltHtmlLwcAura.
  • An avatar (lightning-avatar or lightning:avatar) names the person or the record that it shows in its alternative text. That text becomes the alt of the image (Avatar). Salesforce renders an avatar with no alternative text as a decorative image, so leaving the attribute out is allowed. An avatar whose alternative text is set to an empty value is reported at severity 4, as an empty alt is. Enforced by ImageWithEmptyAltHtmlLwcAura.
  • A merge expression or a bound property in the attribute (alt="{!$Label.Logo}", alt={logoLabel}, alternativeText="{!v.logoLabel}") counts as text. The rules cannot know its value when the page runs, so the developer makes sure that it is never empty for an image that carries information.
  • Every image button, icon button, icon-only Lightning button, and image-map link has a name that a screen reader can announce, and the name says what the control does: "Close", not "X icon". An empty value gives the control no name, so it is as bad as a missing one. A control with no name, or with an empty one, blocks the pull request (severity 2). Enforced by ButtonWithoutNameVf and ButtonWithoutNameHtmlLwcAura.
  • A Visualforce page renders as HTML, so an image map in a page follows the same standard. Visualforce has no component for an image map, and a page uses the HTML map and area elements, which ButtonWithoutNameVf reads.

Where each control takes its name from:

Control Its name
input type="image", and apex:commandButton with an image alt (technique H36)
area with an href alt (technique H24)
lightning-button-icon, lightning-button-icon-stateful alternative-text (Button Icon)
lightning:buttonIcon, lightning:buttonIconStateful alternativeText
lightning-button, lightning:button with an icon label
Any of them aria-label or aria-labelledby; on an apex:commandButton, html-aria-label or html-aria-labelledby (apex:commandButton)

What the Rules Do Not Check

These elements are not checked by a rule. Reviewers check the ones that carry meaning.

  • Icons. lightning-icon, lightning-dynamic-icon, the icon of a lightning-badge or of a lightning-vertical-navigation-item-icon, and their Aura forms, draw their icon hidden from assistive technology, and read out the alternative text only when it has a value. With no attribute and with an empty one, an icon renders the same. Salesforce lets a decorative icon leave the attribute out (Icon). An icon that carries meaning has alternative text that says what it means, such as "Approved" (Using Images and Icons). An icon can be decorative at one screen size and carry meaning at another, so an icon left without alternative text is checked on small screens as well as wide ones. No rule checks this. Reviewers do.
  • Components that Salesforce names itself. lightning-button-menu, lightning-helptext, and lightning-barcode-scanner are given a name, such as "Show menu" or "Help", when the attribute is left out.
  • Spinners. A spinner (lightning-spinner or lightning:spinner) is a status message, not an image or a button. The alternative text of a spinner says what is loading. No rule checks this. Reviewers do.
  • Beta components. lightning-illustration and lightning-empty-state.
  • Elements named another way. An svg, an element with role="img", an object, and a canvas get their name from a title, an aria-label, or their fallback content, not from an alt.
  • Text from JavaScript. Alternative text that a component takes from JavaScript data, such as the items of a lightning-pill-container or an icon column of a lightning-datatable, is outside the markup that the rules read.
  • Visualforce email templates. These rules do not read Visualforce email templates (.email files): they are Salesforce Classic templates, which can be edited only in Classic, and Lightning email templates take their place in Lightning Experience (Use Visualforce Email Templates with Guest Users).

The rules for HTML, Lightning Web Components, and Aura read Aura markup through Code Analyzer. With PMD alone, Aura markup needs a second run (Appendix F.2: Aura Markup and PMD Alone).

Documentation

The code says what it does. Documentation says what the code is for and why it is written that way, which the code cannot say. The next developer, a reviewer, and an auditor do not have the context of the person who wrote it, and a tool or an agent that reads the code or its metadata learns from the description as well.

Comments

The standards below apply to every language. Each language page adds what is particular to it: Apex, HTML and Visualforce, JavaScript, and Lightning Web Components and Aura.

  • Code is commented where a comment helps the next reader, in every language and in addition to ApexDoc. A comment says why the code is written as it is, and explains what a reader would otherwise have to work out: a step that is not obvious, a workaround and what it works around, the reason for an order of steps, or a limit that shaped the code. No rule checks this. Reviewers do.
  • A comment says why, not what. A comment that only repeats the code adds nothing, and it can fall out of step with the code when the code changes. No rule checks this. Reviewers do.
  • An exception to a blocking rule is explained in the code. Where a reviewer will have to accept a blocking violation, such as a query in system mode, a comment on the lines above it or in the method's header acknowledges the exception and gives the reason. The reviewer then has the context to accept it without asking the developer for an explanation, and a comment in the code, unlike the description of a pull request, is there for every later review of the same code. No rule checks for the comment. Reviewers do.
  • A comment is not where unfinished work is kept. TODO, TO-DO, FIXME, FIX-ME, or HACK as the first word of a comment line, or anywhere in a comment with a colon after it (// Note: TODO: retry), marks unfinished work, which is finished, or recorded where the team tracks its work, and then removed. TODO and FIXME are matched in any case, and the other three in capitals only, so that "to-do" and "hack" in an ordinary sentence are not reported. One that remains is reported at severity 5. Enforced by NoUnresolvedTodosApex, NoUnresolvedTodosJavaScript, and NoUnresolvedTodosMarkup.
  • No violation is suppressed without an approving review from the owning team (No Scanner Suppressions). A suppression in the code blocks the pull request (severity 2) until the violation is fixed or the owning team approves the suppression. Enforced by NoUnapprovedSuppression, NoUnapprovedEslintSuppression, @lwc/lwc-platform/no-inline-disable, and @salesforce-ux/slds/reduce-annotations, for the suppression comments of the SLDS linter (SLDS 2: Suppression Comments).
  • A header comment names a real author, a person or a team, never a default that a tool wrote.

    The Salesforce Documenter (SFDoc) extension for VS Code, which is no longer maintained, fills in ChangeMeIn@UserSettingsUnder.SFDoc as the author and as the last user to modify the file, until its user name setting is changed. The default looks like an author and names nobody. That string in any file blocks the pull request (severity 2). Enforced by NoSfdocDefaultAuthor.

Naming

The naming standards of each language, in one place. The naming of metadata is on the Metadata Standards page.

Apex

  • Names follow the standard Apex conventions: classes, interfaces, and enums in Pascal case (AccountService); methods, fields, parameters, and local variables in camel case (findAccounts); and constants and enum values in upper case with underscores (MAX_RETRIES). A name that breaks the convention blocks the pull request (severity 1). Enforced by ClassNamingConventions, MethodNamingConventions, FieldNamingConventions, FormalParameterNamingConventions, and LocalVariableNamingConventions.
  • An annotation is written in Pascal case, as the Apex Developer Guide writes every annotation (Annotations): @IsTest, not @isTest, and @TestVisible, not @testVisible. Apex reads an annotation in any case, and Prettier leaves it as it was typed, so the developer writes it. An annotation in another case blocks the pull request (severity 1). Enforced by AnnotationsNamingConventions.
  • No class, enum, or interface takes the name of a type in the System or Schema namespace, such as Database, Url, Callable, or DisplayType (System Namespace, Schema Namespace). Code that means the built-in type can reach the local one instead, and a reader cannot tell which of the two it means. A top-level type with such a name blocks the pull request (severity 2). Enforced by TypeShadowsBuiltInNamespace. The rule does not read the name of an inner class, so reviewers check it.
  • A variable's name is not one letter: a local variable, a loop variable, a parameter, a field, a property, and the variable of a catch block each have a name of two or more letters that says what it holds (account, not a). Three names are allowed, each in its place, because every Apex developer reads them the same way: i and j as the counter of a for loop, and e as the variable of a catch block. A one-letter name is reported at severity 3. A test class is not checked by default, and an organisation that wants it checked sets the reportInTestClasses property to true. An organisation that allows other one-letter names lists them in the allowedNames property, and they are then allowed for every kind of variable. Enforced by NoSingleLetterVariableName.

Class/Trigger

  • A class name ends with a suffix that says what the class is: Test or Tests for a test class, Batch for a Batchable class, Queueable for a Queueable class, Sched or Schedulable for a Schedulable class, SchedBatch for a class that is both Schedulable and Batchable, Exception for a class that extends Exception, and Constants for a class that only holds constants. The test class suffix is checked by a rule: a test class whose name ends otherwise blocks the pull request (severity 1). Enforced by ClassNamingConventions, through its testClassPattern property. No rule checks the other suffixes. Reviewers report a missing one at severity 3, which does not block.
  • A class that a Lightning component calls ends in Controller. The @AuraEnabled annotation makes a method available to Lightning web components and Aura components (AuraEnabled Annotation), so the name tells a reviewer that the class can be reached from a browser. A top-level class with an @AuraEnabled method and another name is reported at severity 4. Enforced by AuraEnabledControllerNaming. A class that only has @AuraEnabled fields is a data class and is not reported. The custom controller and the extensions of a Visualforce page are named the same way (Controller Names).
  • A class is a test class because of its @IsTest annotation, not its name (Test Classes). Every test class still ends in Test or Tests, so that a reader can tell it from a production class by its name alone.

  • An interface's name starts with I (ILogger), and so does the name of an interface nested in a class. An interface named otherwise blocks the pull request (severity 1). Enforced by ClassNamingConventions, through its interfacePattern and innerInterfacePattern properties.

  • An abstract class's name starts with Abstract (AbstractIntegrationService), as an interface's name starts with I. The prefix tells a reader that no code creates an instance of the class and that a subclass completes it. A top-level abstract class named otherwise blocks the pull request (severity 1). Enforced by ClassNamingConventions, through its abstractClassPattern property. The property reads a top-level class only, so reviewers check an abstract class nested in another.
  • ClassNamingConventions has seven pattern properties, and the stock rules sample sets all of them. Three keep PMD's default, Pascal case ([A-Z][a-zA-Z0-9_]*): classPattern, innerClassPattern, and enumPattern. Four carry the standards above: testClassPattern ([A-Z][a-zA-Z0-9_]*Tests?), interfacePattern and innerInterfacePattern (I[A-Z][a-zA-Z0-9_]*), and abstractClassPattern (Abstract[A-Z][a-zA-Z0-9_]*).
  • A trigger's name ends in Trigger (AccountTrigger), or in the suffix the organisation sets in the rule's property, such as s for a trigger named after the plural of its object (Accounts). An org uses one convention for every trigger, never more. A trigger whose name ends otherwise is reported at severity 3. Enforced by TriggerNameSuffix. The trigger body is one line whatever its name (Triggers).
  • A trigger action class is named TA_, the object, an underscore, and what the action does: TA_Account_SetDefaults. The Trigger Actions Framework names its own examples this way (TA_Opportunity_StageInsertRules). A class that serves every object uses SObject as the object (TA_SObject_PreventDeletion in Preventing Record Deletion). No rule checks this. Reviewers report a name that breaks it at severity 3, which does not block.
  • A class with the @RestResource annotation ends in RestResource, and starts with the object it serves when it serves one: AccountRestResource (RestResource Annotation). The annotation makes the class a REST endpoint, and the name tells a reviewer that callers outside the org can reach it, as Controller does for a class that a Lightning component calls. No rule checks this. Reviewers report a name that breaks it at severity 3, which does not block.

Method

  • A method's name should start with a verb that says what the method does: findAccounts, createInvoice, validateAddress, isOverdue, hasAccess, or canEdit. A name that is only a noun, such as accounts(), does not say whether the method reads, changes, or checks. No rule checks this. Reviewers do, and it does not block a pull request.
  • A method that returns a Boolean should be named as a question, such as isActive or hasAccess, not getActive. A method with no parameters whose name starts with get and that returns a Boolean is reported at severity 4. Enforced by BooleanGetMethodNameApex.

Test Method

  • Underscores in a test method name are allowed and discouraged, and no other method name may contain one. Enforced by MethodNamingConventions, which allows them in test methods only.

Test methods have long been written with underscores, such as findAccounts_noMatch_returnsEmptyList, so these standards are lenient on purpose, to make adoption easier. An organisation should remove them as it revises its codebase.

Property/Field/Variable

  • Property names follow the same camel case convention. A property name that breaks it blocks the pull request (severity 1). Enforced by PropertyNamingConventions.
  • FieldNamingConventions has five pattern properties, and the stock rules sample sets each at PMD's default. constantPattern and enumConstantPattern are upper snake case ([A-Z][A-Z0-9_]*), for a constant (static final) and an enum value. finalPattern, instancePattern, and staticPattern are camel case ([a-z][a-zA-Z0-9]*). So the enum value PENDING_APPROVAL passes, and Approved or pendingApproval blocks the pull request (severity 1).
  • No Hungarian notation. The name of a variable, field, property, or parameter does not start with a prefix for its type: familyName, not sFamilyName; member, not oMember; isLegal, not bIsLegal. The prefix makes a name harder to read, and it is unnecessary: an editor such as VS Code shows the type of any variable when the pointer rests on it. The prefixes are also extra characters of Apex, and a very large org can reach the limit on the total amount of Apex code it may hold (Execution Governors and Limits). No rule checks this. Reviewers encourage it, and it does not block a pull request.
  • A simple accessor is an Apex property ({ get; set; }). A get or set prefix is allowed on a method that fetches or computes a value. No rule can reliably tell an accessor from any other method.
  • A collection's name should say what it holds. A list is named for its items in the plural (accounts, relatedContacts). A map is named {value}By{key} (accountsById). A set of record Ids is named for the record in the singular, then Ids (accountIds). No rule checks this. Reviewers do, and it does not block a pull request.

Type/Enum

  • A type is written as a class is named, in Pascal case, wherever it is used: String, not string, and List, not LIST. The same goes for every other type. Apex reads any case (Formatting), and Prettier leaves a name as it was typed, so the developer writes it. No rule checks this. Reviewers do.
  • A field or property does not share its name with a method of its class, or with the class itself. Apex compiles both, because methods and variables have their own namespaces within a class (Naming Conventions), and it compares names without regard to case, so a property Summary and a method summary() have the same name. A reader cannot tell the two apart at a glance. The same goes for a local variable with the name of a class in its file: in Line line = new Line(); the variable reads as the class. Each is reported at severity 4. Enforced by FieldNameMatchesMethodName and FieldNameMatchesClassName.

The platform cannot always tell them apart either. Apex evaluates local variable names before class names, so a local variable with the name of a class hides that class's methods and variables: with a variable named Database in scope, Database.insert(...) no longer compiles (Static and Instance Methods, Variables, and Initialization Code). A variable named after an sObject type hides it the same way, in any case, where the name starts a dotted expression: with String account in scope, Account.SObjectType and the field token Account.Name no longer compile, while Schema.Account.SObjectType and new Account() still do.

  • A type suffix on a variable or a class (accountsToUpdateList, AccountWrapper) is allowed, and never required.

The same conventions by kind of code, each with an example:

Code type Naming convention
Class Case: Pascal case (AccountService)
Interface Case: Pascal case.
Prefix: I (ILogger)
Enum Case: Pascal case (ShippingStatus)
Enum value Case: upper snake case (IN_TRANSIT)
Test class Suffix: Test or Tests (AccountServiceTest)
Batch class Suffix: Batch (AccountCleanupBatch)
Queueable class Suffix: Queueable (ExternalSyncQueueable)
Schedulable class Suffix: Sched or Schedulable (AccountCleanupSched)
Schedulable and Batchable class Suffix: SchedBatch (AccountCleanupSchedBatch)
Constants class Suffix: Constants (BillingConstants)
Abstract class Prefix: Abstract (AbstractIntegrationService)
Exception class Suffix: Exception (AccountServiceException)
Trigger action class Prefix and pattern: TA_{SObject}_{Action} (TA_Account_SetDefaults)
REST resource class Suffix: RestResource (AccountRestResource)
Trigger Suffix: Trigger, or the one the organisation sets; one convention for the org, never more (AccountTrigger)
Method Case: camel case, starting with a verb (findAccounts)
Test method Case: camel case; underscores allowed and discouraged (findAccounts_noMatch_returnsEmptyList)
Property Case: camel case (accountName)
Apex Field/Variable Case: camel case (retryCount)
Constant Case: upper snake case (MAX_RETRIES)
Parameter Case: camel case (accountIds)
Local variable Case: camel case (openCases)
List Plural noun (relatedContacts)
Map Pattern: {value}By{key} (accountsById)
Set of record Ids Singular noun and Ids (accountIds)
Annotation Case: Pascal case (@IsTest)

JavaScript

TODO

Stub. The naming standards of JavaScript are not written yet.

Visualforce

  • A Visualforce page or component is named in Pascal case, such as AccountSummary.

Controller Names

The Apex class behind a Visualforce page is named for its role, so that a reader of the class knows that a page is bound to it.

  • A custom controller's name ends in Controller, and a controller extension's name ends in Extension or Ext. They are the classes named in the controller and extensions attributes of the apex:page tag (apex:page). A name that ends otherwise is reported at severity 4. Enforced by VisualforceControllerNaming.

A class that a Lightning component calls is named the same way (Naming).

Rules

The rules that this page mentions. The Standard column gives each rule's standard in one line, and the Section column links to the section that mentions the rule; "Not yet written" marks a rule of this page that no section mentions yet. Appendix F.6 holds the full cross-reference.

Rule Kind Severity Standard Section
AnnotationsNamingConventions Stock PMD 1 Write annotations in Pascal case: @IsTest, not @isTest Apex
ClassNamingConventions Stock PMD 1 Class, interface, and enum names are in Pascal case Apex
FieldNamingConventions Stock PMD 1 Field names are in camel case, and constant and enum value names in upper snake case Apex
FormalParameterNamingConventions Stock PMD 1 Parameter names are in camel case Apex
LocalVariableNamingConventions Stock PMD 1 Local variable names are in camel case Apex
MethodNamingConventions Stock PMD 1 Method names are in camel case; underscores are allowed Apex
PropertyNamingConventions Stock PMD 1 Property names are in camel case Apex
@lwc/lwc-platform/no-inline-disable Stock Code Analyzer ESLint 2 No comment that switches off an ESLint rule for a line Comments
@salesforce-ux/slds/reduce-annotations Stock Code Analyzer ESLint 2 No SLDS linter suppression without an approving review from the owning team Comments
ButtonWithoutNameHtmlLwcAura Custom PMD XPath 2 Every image button, icon button, and image-map link has a name that a screen reader can announce Buttons and Links
ButtonWithoutNameVf Custom PMD XPath 2 Every image button and image-map link in a Visualforce page has a name that a screen reader can announce Buttons and Links
ImageWithoutAltHtmlLwcAura Custom PMD XPath 2 Every image has an alternative text attribute Images
ImageWithoutAltVf Custom PMD XPath 2 Every image in a Visualforce page has an alt attribute Images
NoSfdocDefaultAuthor Custom Code Analyzer Regex 2 The author is a person or a team, never a tool default such as ChangeMeIn@UserSettingsUnder.SFDoc Comments
NoUnapprovedEslintSuppression Custom Code Analyzer Regex 2 No ESLint suppression without an approving review from the owning team Comments
NoUnapprovedSuppression Custom Code Analyzer Regex 2 No PMD or Code Analyzer suppression without an approving review from the owning team Comments
TypeShadowsBuiltInNamespace Stock PMD 2 No class, enum, or interface takes the name of a System or Schema type Apex
NoSingleLetterVariableName Custom PMD XPath 3 No one-letter variable names, except i and j as loop counters and e in a catch block Apex
TriggerNameSuffix Custom PMD XPath 3 A trigger name ends in Trigger, or in the one suffix the organisation sets Notes on this rule. Apex
AuraEnabledControllerNaming Custom PMD XPath 4 A class that a Lightning component calls ends in Controller Apex
BooleanGetMethodNameApex Custom PMD XPath 4 A Boolean method is named as a question, not with get Apex
FieldNameMatchesClassName Custom PMD XPath 4 A field or property does not take the name of its class, and a local variable does not take the name of a class Apex
FieldNameMatchesMethodName Custom PMD XPath 4 A field or property does not share its name with a method of its class Apex
ImageWithEmptyAltHtmlLwcAura Custom PMD XPath 4 Only a decorative image has an empty alternative text Images
ImageWithEmptyAltVf Custom PMD XPath 4 Only a decorative image in a Visualforce page has an empty alt Images
VisualforceControllerNaming Custom PMD XPath 4 A custom controller's name ends in Controller, and a controller extension's in Extension or Ext Notes on this rule. Controller Names
NoUnresolvedTodosApex Custom Code Analyzer Regex 5 No TODO, TO-DO, FIXME, FIX-ME, or HACK comment stays in Apex Comments
NoUnresolvedTodosJavaScript Custom Code Analyzer Regex 5 No TODO, TO-DO, FIXME, FIX-ME, or HACK comment stays in JavaScript Comments
NoUnresolvedTodosMarkup Custom Code Analyzer Regex 5 No TODO, TO-DO, FIXME, FIX-ME, or HACK stays in a markup comment Comments