Skip to content

Appendix F: Rules

This appendix holds every rule that enforces these standards: the stock PMD rules that the reference adopts, the custom rules of the reference, in three kinds, and the severity levels that all of them share.

The reference writes no rule of its own for ESLint, the Flow engine, or the other engines of Salesforce Code Analyzer, but it relies on rules that ship with them, and each is listed beside the rules of the reference in Appendix F.6. The ESLint rules that these standards set are on the Version Control page (ESLint), and How an ESLint Rule Reaches a Pull Request says how they reach the quality gate.

The reference ships as {N} ruleset files, and each name below is a link to the file in full, on a page of its own: pmd-custom-rules-apex.xml, pmd-custom-rules-markup.xml, pmd-custom-rules-metadata.xml, and pmd-custom-rules-flow.xml, the custom XPath rules of the F.2 page for Apex, for Visualforce and Lightning markup, for XML metadata, and for Flows, Process Builder, and Workflow Rules; pmd-custom-rules-javascript.xml, the custom XPath rules for JavaScript, also on the F.2 page; pmd-stock-rules.xml, the stock PMD rules that the reference adopts; and pmd-stock-rules-javascript.xml, the stock PMD rules for JavaScript, which have a file of their own (Appendix F.1 says why).

A single file, pmd-all-rules.xml, imports the others with one line each, for a run of PMD by itself: PMD loads every rule of an imported file with the priorities and the properties set there, so one --rulesets argument runs them all. Its last line is the ruleset of the Java rules, which travels inside the JAR, so the JAR must be on PMD's classpath for that run; with the JAR absent, PMD loads no rule at all from the file and says nothing, and the file's comment says so.

With our required rules, Code Analyzer does not read that file (which would seem easier). It lists the four files one by one, because the Apex and the JavaScript rules that share a name would be interpreted as one name again in one ruleset, and Code Analyzer would run only the first. It is unfortunate that PMD has multiple stock rules with the same name, and that is why this workaround is needed.

Regex rules are not in any of the files: they are defined inside code-analyzer.yml (Appendix F.4).

The PMD Folder

In a project repository the ruleset files and the JAR of the Java rules sit together in config/pmd/, and code-analyzer.yml, at the root, names the four that it reads (Repository Structure).

config/pmd/
├── pmd-stock-rules.xml                   # The stock PMD rules this reference adopts, with their priorities
├── pmd-stock-rules-javascript.xml        # The stock PMD rules for JavaScript, in a file of their own
├── pmd-custom-rules-apex.xml             # The custom rules for Apex, written in XPath
├── pmd-custom-rules-markup.xml           # The custom rules for Visualforce and Lightning markup
├── pmd-custom-rules-metadata.xml         # The custom rules for XML metadata
├── pmd-custom-rules-flow.xml             # The custom rules for Flows, Process Builder, and Workflow Rules
├── pmd-custom-rules-javascript.xml       # The custom rules for JavaScript, written in XPath
├── pmd-all-rules.xml                     # The seven files above, imported into one, for a run of
│                                         # PMD by itself. Code Analyzer does not read it
└── pmd-java-rules-1.0.0.jar              # The custom rules written in Java. The ruleset that names
                                          # them, rulesets/devsecops-java.xml, is inside the JAR

The Java source is not in a project repository. It is the Maven project of Appendix F.3, which lives in the configuration repository (Appendix D) and builds the JAR that each project receives.

Three Kinds of Custom Rules

PMD offers two ways to write a rule, and Salesforce Code Analyzer adds a third. An XPath rule is a query written inside the ruleset XML. A Java rule is a compiled class that a ruleset names. A regex rule is a pattern, defined in code-analyzer.yml, that is matched against the text of a file. This appendix uses all three. Each has advantages the others lack.

XPath rule Java rule Regex rule
Written as A query inside the ruleset XML A class in a Java project, named by a ruleset A pattern inside code-analyzer.yml
Can see The one file being checked, as PMD's parser exposes it The same file through PMD's API, other files beside it, and parser detail that XPath cannot reach The text of the one file, with no syntax
Tested by PMD's Rule Designer, or a PMD run on sample files PMD's rule-test harness, run by mvn test on every change A Code Analyzer run on sample files
Deployed as A ruleset file listed under custom_rulesets A JAR on PMD's classpath, and the ruleset inside it An entry under engines.regex.custom_rules
Changed by Editing the XML Changing the code and rebuilding the JAR Editing the YAML
Runs under PMD on its own, and Code Analyzer PMD on its own, and Code Analyzer Code Analyzer only
In this appendix Appendix F.2 Appendix F.3 Appendix F.4

Rules Written in XPath

An XPath rule is one query: every node the query selects is a violation. The query, the message, the description, and the priority sit together in the ruleset file, so the file is the whole of the rule.

  • Nothing to build. The ruleset is a text file. Adding or changing a rule is an edit to that file, and PMD reads it as it is.
  • Easy to review. A change to a rule appears in a pull request as a few lines of XML beside the rule's own description, and a reviewer can read it without building anything.
  • Quick to write and to try. A query on PMD's syntax tree can be tried in PMD's Rule Designer against a sample class, and any query can be tried by running PMD on a sample file.
  • The same technique covers code and metadata. One kind of rule checks Apex, Visualforce, and Lightning markup on PMD's syntax tree and, through DomXPathRule, any XML metadata file. Most of the rules in this appendix are XPath rules.
  • One file to deploy. The ruleset is listed under engines.pmd.custom_rulesets in code-analyzer.yml, and nothing else is needed.

Its limit is its reach. An XPath rule sees one file, as PMD's parser presents it. It cannot read a second file, and it cannot see detail that the parser records but does not expose to XPath.

Rules Written in Java

A Java rule is a class that walks the same syntax tree through PMD's own API, with everything else Java can do.

  • It can see what XPath cannot. PMD's parser records the access mode of a DML statement but does not expose it to XPath (pmd/pmd#7108). A Java rule asks the parser directly, and the access mode standard is enforced that way.
  • It can read another file. A rule about an Apex class can open the -meta.xml file beside it, where the class's API version is stored. None of the rules here does: the sharing and access mode standards are the same at every API version (Apex: Be Explicit Everywhere).
  • It can reason, not only select. Conditions, helper classes shared between rules, and a message chosen for each violation are ordinary code. RequireAccessMode gives a different message for a query, a method call, and a DML statement, and FlagSystemMode has a property that decides whether test classes are reported.
  • It has automated tests. PMD's rule-test harness runs each rule against snippets of Apex with the expected violations and line numbers, so a change to a rule can be tested on every pull request.

Its cost is the build. A Java rule needs a Java project (Maven here), a JAR built and delivered to wherever PMD runs, a PMD version kept in step with the one Salesforce Code Analyzer bundles, and a reviewer who reads Java.

Rules Written as Regex Patterns

A regex rule is one pattern: every match is a violation. The pattern, the file extensions it scans, the description, the message, and the severity sit together in code-analyzer.yml, so the entry is the whole of the rule.

  • Nothing to build, and no ruleset file. The rule is a few lines of YAML in the configuration file that the project already has.
  • It reads any file. PMD parses the languages it knows. A pattern reads whatever text it is pointed at, so one rule can cover Visualforce, Lightning markup, and metadata XML together.
  • It sees what no parser exposes. An ordinary comment in Apex, or the JavaScript inside a Visualforce script block, is invisible to an XPath rule and plain text to a pattern.

Its limit is that it sees only text. A pattern has no context: it cannot tell code from a string or a comment that holds the same characters. And it does nothing under PMD on its own, because the regex engine is Code Analyzer's. So the reference keeps regex for what no PMD rule can see at all.

Choosing Between Them

Write a rule in XPath when XPath can express the whole check, and in Java when it cannot. A check that XPath can only half express belongs in Java whole. The access mode rules are the example: XPath could check queries and method calls but not DML statements, so the standard is enforced by {N} Java rules that cover every case, with no XPath rule beside them.

A Code Analyzer regex rule is not a way around XPath's limits where a Java rule can do the job. The access mode of a DML statement is out of XPath's reach, and a pattern could find it in the text of a class, but it would have no context. A Java rule reads the mode from the parser, so it cannot mistake a comment for code. Regex is kept for what no PMD rule can see at all, such as an ordinary comment (Appendix F.4).

For worked examples of both kinds beyond this reference, Salesforce publishes the source of the AppExchange rules that ship with Code Analyzer. In Code Analyzer 5.16.0 they are {N} rules written in XPath and {N} written in Java, with a test for each: the ruleset files, the Java classes, and the tests. Its XML rules are written with PMD's XPathRule, on the tree PMD wraps around an XML file, where the metadata rules in this reference use DomXPathRule on the document itself, as PMD recommends for XML (How the Rules Are Written). An expression from one does not work unchanged in the other.

What an Organisation Can Change

Every rule in this appendix is a recommendation and a tool. An organisation that sees a standard differently changes the rule to suit, and does not have to rewrite it.

  • Its severity. code-analyzer.yml sets the severity of any rule, so a rule that blocks here can be advisory there, or the reverse.
  • Its properties. A property holds a value that a rule works with, such as the lowest API version allowed or the list of metadata types that need approval, and a ruleset can set it without touching the rule (Using and Changing the Ruleset). The properties of every PMD rule, custom or stock, give this flexibility. A regex rule has no property: its pattern and the file extensions it scans are in its definition, and are changed there (Using and Changing the Rules).
  • Whether it runs. A rule that an organisation does not want is disabled in code-analyzer.yml.

The Stock PMD Rules

PMD ships its own rules, and Appendix F.1 lists the ones this reference adopts. The {N} stock rules files adopt them by reference: every Apex, JavaScript, HTML, and Visualforce rule that PMD ships, except those listed under Stock PMD Rules Not Adopted, each with an explicit priority and, where its behaviour is changed, a property override. There is nothing to write: the work is setting the priorities, and deciding which rules not to enforce (Stock PMD Rules Not Adopted).

Stock PMD Rules Not Adopted

These standards do not adopt every rule that PMD ships. The stock PMD rules below stay in the stock rules file, each commented out with no priority, on one line or, for a rule with properties, with its properties at PMD's defaults inside the same comment, so that the file accounts for every rule PMD ships and shows which are left out on purpose. Where Code Analyzer would run one of them by default, the sample code-analyzer.yml disables it with disabled: true. {N} of them are rules that PMD has deprecated: a deprecated rule still runs today, with a warning from PMD, and PMD removes it in its next major version, so the reference does not adopt one, and names the rule that replaces it.

Rule Language Why it is not recommended
ExcessivePublicCount Apex These standards set no limit on the number of public members of a class
ExcessiveParameterList Apex These standards set no limit on the number of parameters of a method
ApexCRUDViolation Apex The access mode standard is enforced by RequireAccessMode and FlagSystemMode, and this rule repeats the first
ApexSharingViolations Apex RequireExplicitSharing is more restrictive, because it does not check for and depend on DML: it requires a sharing declaration on every outer class, test classes included, whether or not the class touches data. ApexSharingViolations reports only a class that holds a query, a DML statement, or a Database method call, and does not include tests. It also reports inner classes, where the standard recommends the declaration and does not require it
ExcessiveClassLength Apex Deprecated by PMD in favour of NcssCount, which the stock rules file adopts
NcssConstructorCount Apex Deprecated by PMD in favour of NcssCount
NcssMethodCount Apex Deprecated by PMD in favour of NcssCount
NcssTypeCount Apex Deprecated by PMD in favour of NcssCount
StdCyclomaticComplexity Apex A duplicate of CyclomaticComplexity, which the stock rules file adopts: both count the decision points of a method, and CyclomaticComplexity also counts && and ||, so a complex method was reported twice
GlobalVariable JavaScript ESLint's no-undef, in the sample ESLint configuration, reports the same missing declaration, and Lightning Web Security keeps each component's globals to itself
InaccurateNumericLiteral JavaScript A number literal beyond the precision of a JavaScript number is a rare mistake in component code, and ESLint's no-loss-of-precision also reports it
ScopeForInVariable JavaScript A for...in variable with no declaration is the same missing declaration that ESLint's no-undef reports
UnnecessaryBlock JavaScript The rule was written before JavaScript had block scope, and it also reports braces that scope a let or const, such as the braces of a case that declares a const of its own. ESLint's no-lone-blocks reports only the blocks that do nothing
UnnecessaryTypeAttribute HTML Several kinds of HTML-like file go through the HTML engine, and one of them may need the type attribute, so a redundant one does no harm
UseAltAttributeForImages HTML ImageWithoutAltHtmlLwcAura reports the same missing alt at a blocking severity, matches the tag in any case, and also reads Aura markup
MissingEncoding XML Salesforce writes the XML declaration, with its encoding, on every metadata file it retrieves
MistypedCDATASection XML Salesforce writes the metadata XML, and a mistyped CDATA section in a hand-written file fails the deployment itself

TODO

Reevaluate the explanation for UnnecessaryBlock in the table above (Content item 153).

Severity Levels

Every rule carries a severity from 1 to 5. The severity states how a violation of the rule is treated at the quality gate, whichever tool reports it.

Severity Label At the quality gate
1 Critical Blocking; fixed before the merge, and rarely accepted
2 High Blocking; fixed, or accepted by the owning team
3 Moderate Not blocking; a fix is strongly encouraged
4 Low Not blocking; advisory
5 Info Not blocking; for information only

Severities 1 and 2 block the pull request. Severities 3 to 5 are listed in the non-blocking violations table and do not prevent merging.

A severity 1 violation is also cleaned up on sight. A pull request that edits a file fixes the severity 1 violations in that file, including the ones that were there before the pull request.

How a blocking violation is accepted, and the remediation timelines that an organisation may set for severities 3 and 4, are in the Priority Reference, with how a rule's PMD priority becomes its severity. The policy behind these levels is in Ruleset Files as a Governance Artifact, and the scan that applies them is A.2.9, Static Code Analysis.

Priority Reference

From PMD Priority to Severity

In this reference, a rule's Code Analyzer severity is its PMD priority: the two tools use different names for the same number. A PMD ruleset states each rule's priority, and Salesforce Code Analyzer turns that into a severity of its own, though not to the same number. It derives each severity as shown in the second column, which would leave every priority 2 rule advisory. The organisation's code-analyzer.yml overrides each rule's severity with the same number as its priority, shown in the third column, and that override survives regeneration of the file from each engine's rules and configurations (see Appendix E).

PMD Priority Default Code Analyzer Severity Overridden Code Analyzer Severity
1 2 (High) 1
2 3 (Moderate) 2
3 3 (Moderate) 3
4 4 (Low) 4
5 4 (Low) 5

With --severity-threshold 2 configured in the GitHub Actions workflow, priorities 1 and 2, overridden to severities 1 and 2, produce a non-zero exit code and block the PR. What each overridden severity means at the quality gate is under Severity Levels above.

Accepting a Blocking Violation

A severity 2 violation is fixed, or the author makes a case for leaving it. A large body of older code written to an earlier naming convention is an example: renaming all of it is rarely the work of the pull request that happens to touch it, so the violation may stay. The case is made to a member of the owning team (the code owners for the changed files, not any approver). Their approving review accepts it, and lets the merge proceed with the violation still listed in the pull request's results.

A severity 1 violation is accepted only in rare cases. The case for leaving one must be stronger than at severity 2, and it can mean more work inside the organisation. A standard that expects such a case says who accepts it. An organisation may also require a second approver for a severity 1 violation, for example from a senior team or the CoE. The workflows in this reference do not include a second approver.

Code Analyzer has no per-violation approval of the kind CodeScan offers, and no built-in way to approve a pull request past blocking violations as Copado and CodeScan do, so the approving review stands in for both, and the review is the record of who accepted what.

Leaving a blocking violation to be fixed in a separate pull request is not recommended (Version Control: Fixing a Blocking Violation).

Remediation Timelines

A severity 3 or 4 violation does not block the pull request, but an organisation may give it a deadline. It may set a remediation timeline for severity 3 violations, and perhaps for severity 4: a time within which a reported violation is fixed. No workflow in this reference tracks such a timeline; the organisation's own process does.

The Custom Rules

Every custom PMD rule in this reference, by group, with XPath and Java rules together.

The custom regex rules are not PMD rules, and they have a table of their own, in Appendix F.4; so do the stock PMD rules that the reference adopts, in Appendix F.1. The groups are in alphabetical order, and the rules in each are listed most severe first, then by name. Nearly every rule is written in XPath, in the ruleset on Appendix F.2, and two are written in Java as shown on Appendix F.3.

Each rule name is an anchor that a standard can link to. Which standard each rule enforces is in Appendix F.6.

Code Rules

Accessibility

Rule Language Priority What it does Message
ButtonWithoutNameHtmlLwcAura HTML (LWC and Aura) 2 An image button, an image-map link, a Lightning button icon, or an icon-only Lightning button has a name that a screen reader can announce: alt on an input of type image or on an area with an href, alternative-text or alternativeText on a Lightning button icon, or an aria-label or aria-labelledby attribute, with text. A bound property counts as text. Give this button a name that a screen reader can announce: alt, alternative-text, aria-label, or aria-labelledby with text
ButtonWithoutNameVf Visualforce 2 An image button, an input of type image or an apex:commandButton with an image, and an image-map link, an area with an href, have a name: an alt attribute with text, or an aria-label or aria-labelledby attribute with text. A merge expression counts as text. Give this image button or image-map link a name that a screen reader can announce: an alt, aria-label, or aria-labelledby attribute with text
ImageWithoutAltHtmlLwcAura HTML (LWC and Aura) 2 An img element, or a Lightning carousel image, has its alternative text attribute: alt on an img, alternative-text in a Lightning web component, or alternativeText in Aura. The text says what the image shows, or the attribute is empty when the image is decorative. Add alternative text to this image: alt, or alternative-text on a Lightning component; leave it empty only if the image is decorative
ImageWithoutAltVf Visualforce 2 An img or apex:image element has an alt attribute. The text says what the image shows, or the attribute is empty when the image is decorative. Add an alt attribute to this image: text that says what it shows, or an empty alt if it is decorative
ImageWithEmptyAltHtmlLwcAura HTML (LWC and Aura) 4 An img element, a Lightning carousel image, or a Lightning avatar whose alternative text is empty or blank is hidden from assistive technology, which is right only when it is decorative. A bound property counts as text. Empty alternative text hides this image from screen readers: confirm that it is decorative, or describe it
ImageWithEmptyAltVf Visualforce 4 An img or apex:image element whose alt attribute is empty or blank is hidden from assistive technology, which is right only for a decorative image. A merge expression counts as text. An empty alt hides this image from screen readers: confirm that it is decorative, or describe it

Apex Access, Sharing, and Access Mode

Rule Priority What it does Message
RequireExplicitSharing 1 Every outer class must declare its sharing. The reportInTestClasses property decides whether test classes are reported. Interfaces and enums are never checked, and an inner class without a sharing declaration takes the sharing of the class that calls it, so no declaration is needed there. Notes on this rule. Declare the sharing of this class: with sharing, without sharing, or inherited sharing
RequireOuterClassAccess 1 All outer Apex classes must have an explicit access modifier. Specify public, private, or global on outer Apex classes
DeclareAccessOnMethods 2 Every method that is not a test method must declare an explicit access modifier: instance and static methods, constructors, and the helper and setup methods of a test class. Apex treats a method without a modifier as private, so this rule changes no behaviour; it exists for readability and auditability, so that a reader or reviewer never has to know the default. Notes on this rule. Explicitly use public/private/protected/global on Apex methods
DoNotRunAsCurrentUser 2 Tests must not run as the current user, for example System.runAs(new User(Id = UserInfo.getUserId())). The rule is deliberately over-strict: any UserInfo.getUserId() call inside a System.runAs statement is reported because this is a screening rule. Methods annotated @TestSetup are exempt. Do not execute test methods as the running user - use a dedicated test user instead
DoNotUseWithoutSharing 2 Flags any class declared as without sharing. Use with sharing or inherited sharing, or document the justification for without sharing
FlagSystemMode 2 A system-mode database operation bypasses object and field permissions, so using system mode must be a deliberate, reviewed choice: inline SOQL and SOSL WITH SYSTEM_MODE, Database and Search methods given AccessLevel.SYSTEM_MODE, and DML run as system. The reportInTestClasses property decides whether test classes are reported. Written in Java. Notes on this rule. System-mode database operation: give the reason in a comment above it or in the method header
NoSecurityEnforced 2 WITH SECURITY_ENFORCED is not accepted in a query. WITH USER_MODE replaces it, and is stricter. Replace WITH SECURITY_ENFORCED with WITH USER_MODE
RequireAccessMode 2 Every database operation must state its access mode: inline SOQL and SOSL, the Database and Search methods that accept an AccessLevel, and DML statements, including in test classes. Written in Java. Notes on this rule. State the access mode on every database operation: WITH USER_MODE, AccessLevel.USER_MODE, or as user
RequireInnerClassSharing 3 An inner class should declare its sharing. An inner class with no declaration takes the sharing of the class that calls it, so the declaration is a recommendation and not a need: with it written, a reader knows how the class runs without tracing its callers. Declare the sharing of this inner class: with sharing, without sharing, or inherited sharing

Apex Cookies and Cryptography

Rule Priority What it does Message
HotspotEncrypt 2 Every call that encrypts or decrypts with a key is reviewed for where the key comes from and how it is kept. A reviewer accepts each call once the key is generated or read from a store that the platform protects. Encryption or decryption here needs a security review of where its key comes from and how it is kept
AvoidInsecureCookies 3 A cookie created with isSecure set to false is also sent over a connection that is not encrypted, where it can be read. Create every cookie with isSecure set to true. Notes on this rule. Create this cookie with isSecure set to true
AvoidWeakCryptoAlgorithms 3 MD5 and SHA-1 are not collision resistant, and SHA-1 is being retired for every use. Name an algorithm of the SHA-2 family for a digest, a message authentication code, or a signature. Notes on this rule. Use SHA-256 or a stronger algorithm - MD5 and SHA-1 are not collision resistant

Apex Documentation

Rule Priority What it does Message
NoColonAfterApexDocTag 2 The text of an ApexDoc tag follows the name of the tag, with no colon between them. A colon after a tag, with or without whitespace before it, is read as the first character of the text, so generated documentation shows it. The rule reads every tag, block and inline, whatever its name. Remove the colon after each ApexDoc tag in this comment, and write the text of the tag after its name and a space
NoLastModifiedApexDocTag 2 An ApexDoc comment has no @last modified tag, such as @last modified on or @last modified by, in any case and with or without a colon. No such tag is an ApexDoc tag: a header template wrote it, and version control records who changed a file and when. The tag becomes wrong after the next change that does not update it. Remove the @last modified tags from this ApexDoc comment, because git records who changed the file and when
ApexDocDescriptionLength 4 The description of an ApexDoc comment, the text at its top or the text after a @description tag, is at least 10 characters long, counted from its first letter and including spaces, across the lines it wraps onto. A shorter one, such as a placeholder, says nothing, and is reported without blocking. Notes on this rule. Write a description of at least 10 characters at the top of the ApexDoc comment
DeprecatedDocumentationRequired 4 An element with the @Deprecated annotation has a @deprecated tag in its ApexDoc comment, and the tag has text after it. The text gives the reason for the deprecation and the recommended alternative, so that a developer who uses the element knows what to use instead. A deprecated element with no comment, with no tag, or with an empty tag is reported without blocking. An enum is not read, because PMD does not keep the comment of an enum. Give this deprecated element a @deprecated tag in its ApexDoc comment, with the reason and the alternative
ExceptionDocumentationRequired 4 The ApexDoc comment of a method that throws an exception should name each exception type it throws in a @throws tag, so that a caller knows what to catch. An exception that the method catches itself is not counted, and a method without an ApexDoc comment is not reported. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Name each exception this method throws in a @throws tag of its ApexDoc comment

Apex Error Prone

Rule Priority What it does Message
AbstractClassWithoutAnyMethodApex 1 An abstract class exists to be completed by a subclass, so it declares at least one method or constructor. An abstract class with only fields is a data holder that no code can create, and is not abstract. Add a method to this abstract class, or remove the abstract modifier
RequireInvocableNoArgConstructor 1 From API version 66.0, a class with an @InvocableVariable member and an explicit constructor also has a visible no-argument constructor: public in a class outside a package, global in a global class. The rule reads the class's API version from its -meta.xml and reports only from the version in the minApiVersion property; a class whose version cannot be read is left to the stock rule InvocableClassNoArgConstructor. Written in Java. Notes on this rule. From API 66.0, give this class a public no-argument constructor (global in a global class), so that a Flow can use it
BrokenNullCheckApex 2 A null check protects the expression after it. x != null || x.size() > 0 and x == null && x.isEmpty() both read the variable exactly when it is null, so they throw the error that the check was written to prevent. This null check throws the null error it is meant to prevent; use and in place of or, or or in place of and
NullValueCheckBeforeEmptyCheck 2 A null check placed after an isEmpty() call on the same expression runs too late; the isEmpty() call throws first. Put the null check first. Check for null before calling isEmpty() on a collection, not after
AvoidNestedSwitchStmts 3 A switch statement does not hold another switch statement. The inner one moves to a method of its own, whose name says what it decides. Move the inner switch statement to a method of its own
AvoidRecordTypeInfosByName 3 getRecordTypeInfosByName() finds a record type by its label, which can be changed in Setup at any time. Find it by its developer name, which does not change, with getRecordTypeInfosByDeveloperName(). Look the record type up by its developer name - use getRecordTypeInfosByDeveloperName()
FixDuplicateConditions 3 Each branch of an if / else if chain tests a different condition. A condition that repeats an earlier one in the same chain can never be true when it is reached, so its branch never runs. This condition is the same as an earlier condition in this if statement, so this branch can never run
IdempotentOperationsApex 3 An assignment changes something. An assignment of a variable or a field to itself, such as count = count, does nothing, and is usually a mistake for this.name = name or for another value. This assignment gives a variable its own value, so it does nothing; assign the value that was meant
MissingStaticMethodInNonInstantiatableClassApex 3 A class that code cannot create, because every constructor is private, has a public or global static member, such as a factory method or a constant, or no code can use it. Nothing can use this class - add a public static member, or make a constructor public
PositionLiteralsFirstInComparisons 3 A comparison with a string literal calls the method on the literal: 'abc'.equals(s), not s.equals('abc'), which fails when s is null. Write the string literal first, as in the literal.equals(value), so that a null value cannot cause an error
ReturnEmptyArrayRatherThanNull 3 A method that returns a List, a Set, a Map, or an array returns an empty one, not null, so that every caller can loop over the result without a null check. Return an empty collection in place of null, so that the caller can loop over the result
UnconditionalIfStatementApex 3 An if statement tests something that can change. A condition written as true, false, or a comparison of two literals is always the same, so the if statement runs its block always or never. A constant that holds such a value is not seen. This condition is always true or always false; remove the if statement or the code that never runs
EmptyInitializer 4 An instance initializer block holds the code it was written for. An empty one does nothing, and a reader cannot tell whether code was lost from it. Remove this empty initializer block, or put in it the code it was written for
PreferSwitchOverIfElseChains 4 An if statement whose branches compare one variable with a list of values reads as a list of cases, and a switch statement says that directly: each value is written once, in a when clause, and the variable is written once. The minConditions property sets how many comparisons make the if statement a switch, and it is 3 by default, so a test of one or two values stays an if statement. Comparisons with null are not counted. Check the change: == compares two strings without regard to case, but a when clause on a String is case-sensitive. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). This if statement compares one variable with ${minConditions} or more values. Use a switch statement on the variable
SwitchWithoutWhenElse 4 Every switch statement has a when else block, so that a value the switch does not name is handled on purpose. A switch with no when else is reported and does not block. Add a when else block to this switch statement, to say what happens for a value it does not name
UnusedNullCheckInEqualsApex 4 A null check before an equals() call protects the object whose method is called. a != null && b.equals(a) checks a and calls a method of b, which can still be null. This null check protects the wrong object; call equals on the object that was checked

Apex Hard-Coded Values

Rule Priority What it does Message
AvoidHardcodedIpAddressApex 1 A string literal that is an IPv4 address, or a URL whose host is one, ties the code to one network address. Read the address from a Named Credential, a custom metadata record, or a custom setting. Notes on this rule. Do not hard-code an IP address - read it from configuration
EmailAddressInLiteral 1 A string literal that holds an email address sends mail to one fixed person, or puts a real address in the repository. Read the address from a custom metadata record or a custom setting, or send to a User, a Contact, or a Group. A test class is not read. Do not hard-code an email address - read it from configuration, or address a user or a group
AvoidApiSessionIdInApexString 2 A session Id is a credential. A string in Apex that holds the merge field {!$Api.Session_ID}, for a page or an email to fill in, hands it out. A call to Salesforce goes through a Named Credential. Do not write the session Id into a string - call Salesforce through a Named Credential
AvoidAbsoluteSalesforceUrl 3 A string literal that holds the absolute URL of a Salesforce org is right in one org only, because every sandbox and every production org has a host name of its own. Use a relative URL. Where a URL has to be absolute, build it from URL.getOrgDomainUrl() as a last resort. The allowedSubdomains property holds the subdomains of salesforce.com that may be written out, and each organisation adds its own. Notes on this rule. Do not hard-code a Salesforce URL - use a relative URL, or as a last resort build it from the org domain
AvoidMagicNumbers 4 A number written in the code says nothing about what it means, and when the same value is needed in two places, the two copies can drift apart. A number is declared once, as a static final constant whose name says what it means, and the code uses the constant. The allowedNumbers property lists the numbers that may be written in the code, and it holds 0, 1, and -1 by default. A list index, a loop condition, a when value of a switch statement, and a test class are not reported. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Replace this number with a constant whose name says what the number means

Apex Naming

Rule Priority What it does Message
NoSingleLetterVariableName 3 A variable with a one-letter name says nothing about what it holds, so a reader has to read the code around it to find out. 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 the variable holds. Three names are allowed in their place: i and j as the counter of a for loop, and e as the variable of a catch block. The allowedNames property lists other one-letter names that are allowed for every kind of variable, and it is empty by default. An enum value is not a variable and is not reported. The reportInTestClasses property decides whether a test class is reported, and it is false by default. Give this variable a name of two or more letters
TriggerNameSuffix 3 A trigger's name ends with one of the suffixes in the suffixes property, compared without regard to case, so that every trigger in the org is named the same way. Each organisation sets the list to its own convention. Notes on this rule. End the trigger name with the suffix ${suffixes}
AuraEnabledControllerNaming 4 A top-level class with an @AuraEnabled method is called from a Lightning component, and its name says so. The controllerPattern property holds the pattern the name must match, and each organisation sets its own. Rename this class - a class with @AuraEnabled methods has a name that matches ${controllerPattern}
BooleanGetMethodNameApex 4 A method that returns a Boolean has a name that reads as a question, such as isActive or hasAccess, not getActive. Name this method as a question, such as isActive or hasAccess, in place of get
FieldNameMatchesClassName 4 A field or property with the same name as the class that declares it reads as a reference to the class, and so does a local variable with the name of any class declared in the same file. Apex names are case-insensitive, so two names that differ only in case are the same name. Rename this variable - it has the same name as a class
FieldNameMatchesMethodName 4 A field or property with the same name as a method of its class makes each harder to tell from the other where it is used. Apex names are case-insensitive, so two names that differ only in case are the same name. Rename this field or the method that has the same name

Apex Performance

Rule Priority What it does Message
DmlInLoopAcrossMethods 2 A loop that calls a method which runs DML runs that DML once on every pass, and each statement counts against the limit on DML statements in a transaction. Collect the records in the loop, and run one DML statement after it. The rule reads methods of the same file only, and it matches them by name. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). This loop calls a method that runs DML. Collect the records in a list in the loop, and run the DML once, after the loop
AvoidDescribeInLoops 3 A describe call in the body of a loop is run again on every pass. When the call does not use a variable of the loop, its result is the same each time: make the call once, before the loop. Move this describe call out of the loop - its result is the same on every pass
AvoidSizeOfSoql 3 A count comes from an aggregate query, such as SELECT COUNT() FROM Account, not from size() on a query that loads every record only to count them. Count records with an aggregate query, such as SELECT COUNT(), in place of size() on a query
AvoidNonSelectiveSoqlFilter 4 A WHERE clause with a negative filter (!=, <>, NOT, NOT IN, EXCLUDES), or with a LIKE whose pattern starts with a wildcard, cannot use an index, so the query reads every row of the object, affecting performance and failing on large data volumes. Inline SOQL is read, and so is a query written as a string, wherever the string sits. Notes on this rule. Do not use a negative expression or a leading wildcard in a WHERE clause. It cannot use an index, so the query is not selective and reads the whole table
RegexPatternsMustBeStaticFinal 4 Pattern.compile() and Pattern.matches() compile their regular expression each time they run, and that costs CPU time on every call. A regular expression that is written in the code is compiled once, into a static final Pattern, and the code uses that Pattern. The field also gives the expression a name, and every use of it reads the same definition. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Compile this regular expression once, into a static final Pattern, instead of on every call
RequireLazyLoadingInGetters 4 A getter that queries loads its value lazily: it runs the query only when the stored value is null, and keeps the result. Otherwise every read of a property, and every call of a Visualforce controller getter, runs the query again. Load this value lazily - query it only when it is null, and keep it, so that it is not queried again on every read

Apex Readability

Rule Priority What it does Message
StatementsShouldBeOnSeparateLines 2 Each statement is on its own line. Line coverage counts a line once, so a line with two statements, or a one-line if block, counts as covered when only part of it ran. Prettier never writes these forms. Put each statement on its own line
LogicInversionApex 3 A negated comparison is written with the opposite operator: x != y, not !(x == y), and x >= y, not !(x < y). Use the opposite comparison operator in place of negating the comparison
AvoidTrivialPropertyGetters 4 A method named getX whose body only returns the field x is written as a property instead, such as public String x { get; private set; }. The property keeps the value and the way it is read in one declaration. A Visualforce page binds to the property with the same expression, {!x}, and code that called the method reads the property. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Replace this getter, which only returns a field, with a property
CombineNestedIfStatements 4 An if statement whose only statement is another if statement, when neither has an else, is written as one if statement that joins the two conditions with &&. One condition is easier to read than two levels of nesting, and the code runs in the same cases. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Join this if statement and the if statement inside it into one, with && between the conditions
InefficientEmptyStringCheckApex 4 A test for a blank string uses String.isBlank(s), not s.trim().length() == 0, s.trim().isEmpty(), or s.trim() == '', which build a new string only to test it. Use String.isBlank() in place of trim() to test for a blank string
PreferNullCoalescingOverTernary 4 A ternary that only supplies a default for a null value is written with the null coalescing operator (??), with the safe navigation operator (?.) where the value is a call or a field on an object. The shorter form names the value once, and it says plainly that the value has a default. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Write this null check with the null coalescing operator (??), and with the safe navigation operator (?.) for a call or a field on the value
PreferSafeNavigationOperator 4 An if statement that only checks an object for null, and then makes one call on it or reads one value from it, is written as one statement with the safe navigation operator (?.). The statement says the same thing without the extra block and its indentation. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Replace this null check with the safe navigation operator (?.)
SimplifiedTernaryApex 4 A ternary expression with a Boolean literal as a result is written with && or ||: flag || other, not flag ? true : other. Write this ternary expression with and-and or or-or
SimplifyBooleanReturnsApex 4 An if statement that only returns true or false is written as a return of its condition: return x > 0, not if (x > 0) { return true; } return false. Return the condition itself in place of an if statement that returns true or false
SimplifyConditionalApex 4 A null check next to an instanceof test is removed, because instanceof is false for null: o instanceof Account, not o != null && o instanceof Account. Remove this null check - instanceof is already false for null
UseCollectionIsEmptyApex 4 A collection is tested with isEmpty(): xs.isEmpty(), not xs.size() == 0, and !xs.isEmpty(), not xs.size() > 0. It states the question that the code asks. Use isEmpty() in place of comparing size() with zero
NoUnnecessaryReturnVariables 5 A value that is assigned to a local variable and returned in the next statement should be returned directly. The variable adds a line to read and nothing else. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). Return this value directly instead of storing it in a variable that is only returned

Apex Testing

Rule Priority What it does Message
MisuseOfAssertClass 2 An assertion checks a value that the code under test produced, never only values written into the assertion. Assert.isNotNull on a new instance or a literal cannot fail, and Assert.areEqual on two literals cannot change. This assertion compares values written into it, so its result is fixed; assert a value that the code under test produced
NonUnitTestContainsAsserts 2 An assertion is only in test code. A failed assertion throws an exception that no code can catch, so production code that checks a condition throws its own exception instead. Move this assertion into a test, or replace it with an exception that the code can catch
RequireTestForOrCritical 2 A test class names the classes and triggers it tests in the testFor parameter of its IsTest annotation, or is marked with critical=true. Notes on this rule. Add testFor or critical=true to the IsTest annotation of this test class
UnnecessaryBooleanAssertionApex 2 An assertion checks a value that the code under test produced. An assertion whose result is fixed by true, false, or null written into it, such as Assert.isTrue(true) or Assert.isNull(null), checks nothing. This assertion checks only true, false, or null, so its result is fixed; assert a value that the code under test produced
TestClassWithoutTestCasesApex 3 A test class has at least one test method. A class with the IsTest annotation and no test method is not reported when it is a test helper: when it has a public or global static method, such as a test data factory, or a member with the TestVisible annotation. Add a test method to this test class, or remove the IsTest annotation if it is not test code
UseAreEqualInsteadOfAssertBoolean 3 A test compares two values with Assert.areEqual or Assert.areNotEqual, so that a failure shows both values, not with == or != inside Assert.isTrue or Assert.isFalse. Use Assert.areEqual or Assert.areNotEqual in place of an == or != comparison in a Boolean assertion
UseAreEqualInsteadOfIsTrue 3 A test compares two values with Assert.areEqual or Assert.areNotEqual, so that a failure shows both values, not with an equals() call inside Assert.isTrue or Assert.isFalse. Use Assert.areEqual or Assert.areNotEqual in place of an equals() call in a Boolean assertion
UseIsTrueInsteadOfAreEqual 3 A test checks a Boolean value with Assert.isTrue or Assert.isFalse, not with Assert.areEqual and a Boolean literal. Use Assert.isTrue or Assert.isFalse in place of an equality assertion against a Boolean literal
AvoidUsingTestIsRunningTest 4 Production code does not call Test.isRunningTest(). Code that behaves one way in a test and another way in the org is not tested by its tests. A call in a class with the IsTest annotation is not reported. Do not branch on Test.isRunningTest() in production code; use a mock or a test double instead
RefactorAssert 4 An assertion uses the Assert class, such as Assert.areEqual or Assert.isTrue, not the older System.assert, System.assertEquals, and System.assertNotEquals. Use the Assert class in place of System.assert, System.assertEquals, or System.assertNotEquals
UnitTestsShouldIncludeStartTest 4 A test method calls Test.startTest() and Test.stopTest() around the code under test. The code between them gets its own set of governor limits, and asynchronous work runs when Test.stopTest() is called. Call Test.startTest() and Test.stopTest() around the code under test in this test method
TestClassIsParallel 5 A test class states whether it can run in parallel, with IsParallel=true or IsParallel=false in its IsTest annotation. IsParallel=true runs the class in parallel even when parallel Apex testing is disabled for the org. A class that cannot run in parallel is not reported: one with SeeAllData=true, one that calls Test.getStandardPricebookId(), System.schedule(), or System.enqueueJob(), and one that creates a ContentNote, a User, a GroupMember, or another setup object that cannot be mixed with other sObjects in one DML transaction. Adapted from sca-extra, https://github.com/starch-uk/sca-extra (MIT licence). State IsParallel in the IsTest annotation of this test class: true if it can run in parallel, false if it cannot

Apex Triggers

Rule Priority What it does Message
MultipleTriggersOnObject 1 Each object has at most one trigger, because the order in which two triggers on one object run is not guaranteed. The rule reads the header of every other trigger in the project, in every package directory below the folder with the sfdx-project.json file, and reports each trigger on an object that has another. A trigger whose name contains two underscores is from a managed package, and is neither reported nor counted. Written in Java. Notes on this rule. {0} has another trigger: {1}. Keep one trigger for each object
NoMethodsInTriggers 2 A trigger must not declare methods, with or without an access modifier. Logic belongs in a handler class, where it can be called, tested, and governed like any other code. Move this method out of the trigger and into a handler class

Apex Untrusted Input

Rule Priority What it does Message
AvoidHttpReferer 2 No security decision rests on the HTTP Referer header. Browsers and network devices often leave it out or strip it for privacy, and it says where a request came from, not who sent it. A reviewer accepts a read that makes no such decision. Do not base a security decision on the Referer header - it is often missing and does not say who sent the request
HotspotDeserializeJson 2 Every place that reads JSON is reviewed for where the text comes from and whether it is checked before it is used. A reviewer accepts each one once the source is trusted or the result is validated. Reading JSON here needs a security review of where the text comes from and how it is checked

JavaScript Security

Rule Language Priority What it does Message
RequireSecureCookie JavaScript 2 A cookie set without the Secure attribute can be sent over a connection that is not encrypted, where it can be read. Set every cookie with the Secure attribute. Set every cookie with the Secure attribute

Lightning Out

Rule Language Priority What it does Message
NoLightningOutApplicationAura HTML (LWC and Aura) 3 An Aura application should not be a Lightning Out dependency app, which extends ltng:outApp or ltng:outAppUnstyled. Such an app is Lightning Out (beta), which requires Lightning Locker, and Lightning Web Security does not support it. Lightning Out (beta) requires Lightning Locker - use Lightning Out 2.0 for an external app, or record the reason for this use
NoLightningOutInVisualforce Visualforce 3 A Visualforce page or component should not use Lightning Components for Visualforce, which is built on Lightning Out (beta): no apex:includeLightning, no lightning.out.js, and no call to $Lightning.use. Lightning Out (beta) requires Lightning Locker, and Lightning Web Security does not support it. Lightning Components for Visualforce is built on Lightning Out (beta), which requires Lightning Locker - remove it, or record the reason for this use

Visualforce, LWC, and Aura Markup

Rule Language Priority What it does Message
AvoidHardcodedIdVf Visualforce 1 A record Id belongs to one org, so a link to one is wrong in every sandbox and every other org. Build the link with URLFOR and $Action, or from a field of the record. Do not link to a hard-coded record Id - use URLFOR with $Action, or an expression
AvoidJavaScriptUrlHtmlLwcAura HTML (LWC and Aura) 2 An attribute value that starts with javascript: runs its text as code. Handle the event in the component's JavaScript, and give a link a real address. This rule reads Lightning Web Component templates, Aura markup, and plain HTML files. The allowedPattern property holds the values that may be written, by default javascript:void(0), and each organisation sets its own. Do not use a javascript: URL - handle the event in a script
AvoidJavaScriptUrlVf Visualforce 2 An attribute value that starts with javascript: runs its text as code. Handle the event in a script, and give a link a real address. The allowedPattern property holds the values that may be written, by default javascript:void(0), and each organisation sets its own. Do not use a javascript: URL - handle the event in a script
LayoutTableWithSemanticMarkupHtml HTML (LWC and Aura) 2 A layout table carries no caption, th, summary, headers, or scope. That markup tells assistive technology that the table holds data, and a screen reader then reads the layout as a data table. The markup of a data table inside the layout table does not count. This rule reads Lightning Web Component templates and Aura markup. Notes on this rule. Remove the caption, header cells, and header attributes from this layout table
LayoutTableWithSemanticMarkupVf Visualforce 2 A layout table carries no caption, th, summary, headers, or scope. That markup tells assistive technology that the table holds data, and a screen reader then reads the layout as a data table. The markup of a data table inside the layout table does not count. This rule reads Visualforce pages and components. Notes on this rule. Remove the caption, header cells, and header attributes from this layout table
ServerSideImageMapHtml HTML (LWC and Aura) 2 The ismap attribute makes an image a server-side image map, which sends the coordinates of a click and cannot be worked from the keyboard. Use a client-side map, or separate links. This rule reads Lightning Web Component templates and Aura markup. Do not use a server-side image map - use a client-side map or separate links
ServerSideImageMapVf Visualforce 2 The ismap attribute makes an image a server-side image map, which sends the coordinates of a click and cannot be worked from the keyboard. Use a client-side map, or separate links. This rule reads Visualforce pages and components. Do not use a server-side image map - use a client-side map or separate links
AvoidAbsoluteSalesforceUrlHtml HTML (LWC and Aura) 3 The absolute URL of a Salesforce org is right in one org only, because every sandbox and every production org has a host name of its own. A link is relative, or comes from a bound property that the component's JavaScript builds. This rule reads Lightning Web Component templates and Aura markup. The allowedSubdomains property holds the subdomains of salesforce.com that may be written out, and each organisation adds its own. Do not hard-code a Salesforce URL - use a relative URL or a bound property
AvoidAbsoluteSalesforceUrlVf Visualforce 3 The absolute URL of a Salesforce org is right in one org only, because every sandbox and every production org has a host name of its own. A link is relative, or built from $Site, URLFOR, or $Page. The allowedSubdomains property holds the subdomains of salesforce.com that may be written out, and each organisation adds its own. Do not hard-code a Salesforce URL - use a relative URL, $Site, URLFOR, or $Page
AvoidHardcodedPageLinkVf Visualforce 3 A link to another Visualforce page is written with the $Page global variable. A hard-coded /apex/ path is not adjusted for the path prefix of an Experience Cloud site. Notes on this rule. Link to this page through $Page, not a hard-coded /apex/ path
AvoidHardcodedResourcePathVf Visualforce 3 A static resource is referenced by name through the $Resource global variable, with URLFOR for a file inside an archive. A hard-coded /resource/ path is not adjusted for the path prefix of an Experience Cloud site. Notes on this rule. Reference this static resource through $Resource, not a /resource/ path
AvoidInlineStylesInVisualforce Visualforce 3 Styling belongs in a stylesheet, where it can be reused and reviewed. A style element, and a style, labelStyle, or html-style attribute on an HTML tag or on a Visualforce component, are reported; styleClass and class are not. Move this inline style to a stylesheet
AvoidMultipleFormsVf Visualforce 3 Salesforce calls one apex:form in a page or custom component a best practice. A part of the page that posts on its own uses an apex:actionRegion inside the one form. Use one apex:form on this page, with apex:actionRegion for a part that posts on its own
LayoutTableHtml HTML (LWC and Aura) 3 A table with the role presentation or none is a table used for layout. Lay the content out in CSS, and keep tables for data. This rule reads Lightning Web Component templates and Aura markup. Notes on this rule. Do not use a table for layout - use CSS
LayoutTableVf Visualforce 3 A table with the role presentation or none is a table used for layout. Lay the content out in CSS, and keep tables for data. This rule reads Visualforce pages and components. Notes on this rule. Do not use a table for layout - use CSS
MouseEventWithoutKeyboardEventHtml HTML (LWC and Aura) 3 Every function that a mouse reaches, a keyboard reaches too. An onmouseover has an onfocus, an onmouseout has an onblur, and an onclick has an onkeydown, onkeyup, or onkeypress. An onclick is not reported on an element that already answers the keyboard: a link, an image-map area, a button, a form control, or a summary. An onclick on a component, whose name has a hyphen or a colon, is not reported, because the component handles its own keyboard behaviour. This rule reads Lightning Web Component templates and Aura markup. Give this mouse handler a keyboard equivalent: onfocus for onmouseover, onblur for onmouseout, or a key handler for onclick
MouseEventWithoutKeyboardEventVf Visualforce 3 Every function that a mouse reaches, a keyboard reaches too. An onmouseover has an onfocus, an onmouseout has an onblur, and an onclick has an onkeydown, onkeyup, or onkeypress. An onclick is not reported on an element that already answers the keyboard: a link, an image-map area, a button, a form control, a summary, or a Visualforce component that renders one of them. An apex:outputPanel renders a div or a span, so its onclick is reported. This rule reads Visualforce pages and components. Give this mouse handler a keyboard equivalent: onfocus for onmouseover, onblur for onmouseout, or a key handler for onclick
NestedScriptElement Visualforce 3 The HTML parser gives the text of a script tag a special meaning inside a script block, so the block can end somewhere other than where its author meant. Escape the tag, or load the other script with apex:includeScript. This script block holds a script tag - escape it or load the script another way
NoInlineEventHandlerAura HTML (LWC and Aura) 3 An event attribute in Aura markup names a controller action in an expression. The Content Security Policy of the platform blocks an inline script, so a handler written as a script does nothing. Name a controller action in this event attribute, not a script
VideoWithoutCaptionsHtml HTML (LWC and Aura) 3 A video element has a track element whose kind is captions or subtitles, or that has no kind, which a browser treats as subtitles, so that a viewer who cannot hear the audio can follow it. This rule reads Lightning Web Component templates and Aura markup. Add a captions or subtitles track to this video
VideoWithoutCaptionsVf Visualforce 3 A video element has a track element whose kind is captions or subtitles, or that has no kind, which a browser treats as subtitles, so that a viewer who cannot hear the audio can follow it. This rule reads Visualforce pages and components. Add a captions or subtitles track to this video
AvoidIframeVf Visualforce 4 Visualforce pages and custom iframes are not supported in Lightning Experience on iPad Safari. A page includes a component, or links to the page. A page that embeds a third-party site has no other way, and its owning team accepts the report. Do not frame a page - include a component, or link to the page
FieldsetWithoutLegendHtml HTML (LWC and Aura) 4 A fieldset has a legend that names the group of controls inside it. A legend of a fieldset inside this one does not count. This rule reads Lightning Web Component templates and Aura markup. Give this fieldset a legend
FieldsetWithoutLegendVf Visualforce 4 A fieldset has a legend that names the group of controls inside it. A legend of a fieldset inside this one does not count. This rule reads Visualforce pages and components. Give this fieldset a legend
FrameWithoutTitleHtml HTML (LWC and Aura) 4 A frame has a title attribute that says what the frame holds, so that a screen reader user can decide whether to enter it. This rule reads Lightning Web Component templates and Aura markup. Give this frame a title
FrameWithoutTitleVf Visualforce 4 A frame has a title attribute that says what the frame holds, so that a screen reader user can decide whether to enter it. This rule reads Visualforce pages and components. Give this frame a title
ListItemOutsideListHtml HTML (LWC and Aura) 4 An li belongs in a ul, ol, or menu, and a dt or dd in a dl. Outside a list, a screen reader does not announce the items as a list. A div of dt and dd elements may sit in a dl. A template tag, or a tag with a colon such as aura:iteration, may sit between the list and its items. An li at the root of a component is not reported, because its list can be in the parent. An li with only such tags above it in an aura:application is reported. This rule reads Lightning Web Component templates and Aura markup. Put this list item inside a list element
ListItemOutsideListVf Visualforce 4 An li belongs in a ul, ol, or menu, and a dt or dd in a dl. Outside a list, a screen reader does not announce the items as a list. A div of dt and dd elements may sit in a dl. An apex: tag may sit between the list and its items. In a page, an li with only apex: tags above it is reported. In a component, it is not reported, because its list can be in the page that uses it. This rule reads Visualforce pages and components. Put this list item inside a list element
ObjectWithoutAlternativeHtml HTML (LWC and Aura) 4 An object element holds fallback content, text or markup, for a browser or a reader that cannot show the object. An object that holds only param elements is reported. This rule reads Lightning Web Component templates and Aura markup. Give this object element fallback content
ObjectWithoutAlternativeVf Visualforce 4 An object element holds fallback content, text or markup, for a browser or a reader that cannot show the object. An object that holds only param elements is reported. This rule reads Visualforce pages and components. Give this object element fallback content
RepeatedLandmarkWithoutUniqueLabelHtml HTML (LWC and Aura) 4 When a file has two or more landmarks with the same role, each has an aria-label or aria-labelledby attribute with text, and no two of them have the same text, so that a screen reader user can tell them apart. A landmark is a nav, aside, search, or main element, a form element with an aria-label or aria-labelledby, or an element whose role attribute names a landmark role: banner, complementary, contentinfo, form, main, navigation, region, or search. A form with no name is not a landmark. A bound property or an expression counts as a unique label, because its value is not known. Landmarks in two components that show on one page are not compared. This rule reads Lightning Web Component templates and Aura markup. Give each landmark with this role a unique aria-label or aria-labelledby
RepeatedLandmarkWithoutUniqueLabelVf Visualforce 4 When a file has two or more landmarks with the same role, each has an aria-label or aria-labelledby attribute with text, and no two of them have the same text, so that a screen reader user can tell them apart. A landmark is a nav, aside, search, or main element, a form element with an aria-label or aria-labelledby, or an element whose role attribute names a landmark role: banner, complementary, contentinfo, form, main, navigation, region, or search. A form with no name is not a landmark. A merge expression counts as a unique label, because its value is not known. This rule reads Visualforce pages and components. Give each landmark with this role a unique aria-label or aria-labelledby
TableCellHeaderNotFoundHtml HTML (LWC and Aura) 4 Every id named in a headers attribute is the id of a th in the same table. A name that matches nothing leaves the cell with no header. This rule reads Lightning Web Component templates and Aura markup. Notes on this rule. This headers attribute names an id that no header cell of the table has
TableCellHeaderNotFoundVf Visualforce 4 Every id named in a headers attribute is the id of a th in the same table. A name that matches nothing leaves the cell with no header. This rule reads Visualforce pages and components. Notes on this rule. This headers attribute names an id that no header cell of the table has
TableHeaderWithoutScopeHtml HTML (LWC and Aura) 4 A header cell says which cells it heads: a scope attribute for a row or a column, or an id that cells name in their headers attribute. A th in a layout table is not reported here, because the layout table rules report it. This rule reads Lightning Web Component templates and Aura markup. Give this header cell a scope or an id
TableHeaderWithoutScopeVf Visualforce 4 A header cell says which cells it heads: a scope attribute for a row or a column, or an id that cells name in their headers attribute. A th in a layout table is not reported here, because the layout table rules report it. This rule reads Visualforce pages and components. Give this header cell a scope or an id
TableWithoutCaptionHtml HTML (LWC and Aura) 4 A data table says what it holds, in a caption element, or in an aria-label, aria-labelledby, or aria-describedby attribute with text. A caption of a table inside this table does not count. A table with a presentation role is a layout table, not a data table. This rule reads Lightning Web Component templates and Aura markup. Give this table a caption or an aria-label
TableWithoutCaptionVf Visualforce 4 A data table says what it holds, in a caption element, a caption facet, or an aria-label, aria-labelledby, or aria-describedby attribute with text. This applies to a table element, an apex:dataTable, and an apex:pageBlockTable. A caption of a table inside this table does not count. A table with a presentation role is a layout table, not a data table. This rule reads Visualforce pages and components. Give this table a caption or an aria-label
TableWithoutHeadersHtml HTML (LWC and Aura) 4 A data table marks its header cells with th, so that assistive technology can announce the headers of each cell. A th of a table inside this table does not count. A table with a presentation role is a layout table, not a data table. This rule reads Lightning Web Component templates and Aura markup. Mark the header cells of this table with th
TableWithoutHeadersVf Visualforce 4 A data table marks its header cells with th, so that assistive technology can announce the headers of each cell. A th of a table inside this table does not count. A table with a presentation role is a layout table, not a data table. This rule reads Visualforce pages and components. Mark the header cells of this table with th
VisualforceControllerNaming Visualforce 4 The custom controller and the controller extensions of a Visualforce page are named for their role, so that a class bound to a page can be told by its name. The controllerPattern and extensionPattern properties hold the patterns the names must match, and each organisation sets its own. Notes on this rule. A class named here does not match the naming pattern (controller: ${controllerPattern}; extension: ${extensionPattern})

Metadata Rules

Field Classification and Design

Rule Priority What it does Message
FieldComplianceCategory 3 Every field, standard and custom, must carry a Compliance Categorization (the complianceGroup element: PII, HIPAA, GDPR, PCI, COPPA, CCPA, or None). Fields in the metadata-API object shape (CustomObject/fields) are covered too. Set the Compliance Categorization on this field
FieldDataSensitivity 3 Every field, standard and custom, must carry a Data Sensitivity Level (the securityClassification element: Public, Internal, Confidential, Restricted, or MissionCritical). Set the Data Sensitivity Level on this field
FieldPiiMinimumSensitivity 3 A field whose Compliance Categorization includes PII or HIPAA must have a Data Sensitivity Level of Confidential, Restricted, or MissionCritical. complianceGroup is a semicolon-separated list, so it is matched with a regular expression. A field categorised as PII or HIPAA must have a Data Sensitivity Level of Confidential or higher
MasterDetailReparentable 3 A master-detail field with reparentableMasterDetail set to false, or left out, which has the same effect, locks every child record to the parent it was created under; a data fix then needs a delete and re-create. Whether to allow reparenting is a decision for each organisation, which these standards do not make; the rule reports the field so that the choice is seen. Where reparenting is allowed, control who may reparent through field-level security and validation rules. This master-detail field does not allow reparenting - confirm that this is the choice your organisation has made

Flow Naming

Rule Priority What it does Message
FlowDecisionQuestion 3 The label of a Decision element is the question that the Decision answers, and it ends with a question mark, so that each outcome reads as an answer. Write this Decision label as a question that ends with a question mark
FlowElementVerb 3 The name of a Flow element starts with the verb for its type, so that a reader knows what the element does from its name. The verbs property holds the verb or verbs for each element type. Start this element name with the verb for its type, such as Get for a Get Records element
FlowNameConvention 3 A Flow's name has four parts: a code for the project, application, or business unit that owns the Flow, the object the Flow works on, a code for the kind of Flow, and the purpose, in that order. The object part may be left out when the Flow has no single object. The kind code must agree with how the Flow starts. The businessCodePattern and kindCodes properties hold the codes. Name this Flow with a project, application, or business unit code, the object, the kind code, and a purpose, in that order
FlowResourcePrefix 4 The name of a Flow resource starts with a prefix for its type, so that a reader can tell a single value, a record, and a collection apart, and so that a resource never takes the name of an element. The prefixes property holds the prefixes, and the exemptNames property holds the names that the platform or a framework requires. Start this resource name with the prefix for its type, such as var, rec, or col

Flow, Process Builder, and Workflow Rules

Rule Priority What it does Message
AvoidHardcodedIdFlow 1 A record ID belongs to one org, so a Flow that carries one breaks in every other org. Find the record by a field that is the same in every org, such as a DeveloperName or an external ID, or read it from a custom metadata type or a custom setting. Do not hard-code a record ID in a Flow - find the record by a field that is the same in every org
ScheduledFlowMinimumApiVersion 1 Scheduled-triggered Flows at API version 61.0 and later have higher asynchronous limits; older versions keep the old limits. Scheduled Flows must run on API version 61.0 or later
AvoidFlowActionInLoop 2 An action inside a loop runs once for each item, and each run can make its own queries and DML statements, or count against a limit of its own, such as the limit on emails. Build a collection in the loop, and call the action once, after the loop. Do not call an action inside a loop - call it once, after the loop, with a collection
DoNotRunFlowInSystemMode 2 Flags a Flow whose runInMode is SystemModeWithSharing or SystemModeWithoutSharing. Both bypass field-level security, and the latter bypasses sharing as well; a Flow in system mode needs a documented justification, as without sharing does in Apex. Do not run Flows in system mode; user mode is the default and is preferred
DoNotUseProcessBuilder 2 All Process Builder automations must be migrated to Flow, except those from a managed package. Notes on this rule. Process Builder is deprecated - migrate this automation to Flow
DoNotUseWorkflow 2 Workflow Rules are retired; every Workflow Rule that is not from a managed package, active or inactive, must be rebuilt as a Flow. Notes on this rule. Convert this Workflow Rule to a Flow
FlowGetRecordsWithoutFilter 2 A Get Records element with no filter reads every record of the object, up to the limit on the records a transaction may retrieve. Add a condition that selects only the records the Flow needs. Add a filter to this Get Records element
FlowMissingTriggerOrder 2 Every record-triggered Flow has a trigger order, so that the Flows of one object run in a known order. A Flow with no trigger order runs in the order of its activation date. Give this record-triggered Flow a trigger order
AvoidAbsoluteSalesforceUrlFlow 3 The absolute URL of a Salesforce org is right in one org only, because every sandbox and every production org has a host name of its own. Use a relative URL. The allowedSubdomains property holds the subdomains of salesforce.com that may be written out, and each organisation adds its own. Do not hard-code a Salesforce URL in a Flow - use a relative URL
AvoidNestedFlowLoops 3 A loop inside a loop runs its elements once for every pair of items, so the work grows with the product of the two collections. Filter or sort the inner collection before the loop, or move the work to invocable Apex that uses a map. Do not put a loop inside a loop - this loop contains another
FlowAllowBackAfterRecordChange 3 A user who goes back to a screen before a Create, Update, or Delete Records element, and then forward again, runs the element a second time, so a record can be created or changed twice. A screen after a record change does not offer the Previous button. Do not let the user go back past this record change - hide the Previous button on this screen
FlowGetRecordsWithoutFaultPath 3 A Get Records element can fail, for example when the running user cannot read the object. Without a fault path, a failure ends the Flow with an unhandled fault and the user sees a generic error. Add a fault path to this Get Records element
FlowGetRecordsWithoutNullCheck 3 A Get Records element that finds no record returns null, and the next element that uses its result can fail. Add a Decision that checks the result for null before the Flow uses it. Check the result of this Get Records element for null before the Flow uses it
ObsoleteFlow 3 A Flow whose status is Obsolete has been deactivated. Remove it from version control, or activate it. Remove this inactive Flow, or activate it
FlowGetRecordsAllFields 4 A Get Records element that stores all fields automatically reads every field of the records it finds. Name only the fields that the Flow uses, to reduce the work and the data the Flow holds. Name the fields that this Get Records element needs
FlowNotAutoLayout 4 Auto-layout places and aligns the elements of a Flow, so that every Flow reads the same way. A Flow in free-form layout is reported, and so is an older Flow that records no layout. Notes on this rule. Switch this Flow to auto-layout
ScreenFlowTextRecordId 4 A screen Flow launched from a record receives the whole record when its input variable recordId is a record variable of the object, so it reads the record fields with no Get Records element. Declare recordId as a record variable of the object, not as text
FlowDraft 5 A Flow whose status is Draft or InvalidDraft has never been activated, so it does not run for users. Activate it. The rule is for the developer's information. Activate this Flow - it has never been active

Integration Endpoints

Rule Priority What it does Message
AvoidHardcodedIpAddressMetadata 1 A value in integration metadata that is an IPv4 address, or a URL whose host is one, ties the org to one network address. Use a host name. The rule reads the files whose type is named in its metadataTypes property, and each organisation may add its own types. Notes on this rule. Do not hard-code an IP address in this metadata - use a host name

Metadata Documentation

Rule Priority What it does Message
MetadataDescriptionCustomField 2 Every custom field must have a description of more than 10 characters. Add a description of more than 10 characters to explain this custom field
MetadataDescriptionCustomObject 2 Every custom object must have a description of more than 10 characters. Add a description of more than 10 characters to explain this custom object
MetadataDescriptionFlow 2 All Flows, Flow tests, Flow categories, and Flow value maps must have a description of more than 10 characters. Add a description of more than 10 characters to explain the purpose of this Flow or Flow component
MetadataDescriptionPermission 2 All Permission Sets, Profiles, Custom Permissions, Permission Set Groups, Muting Permission Sets, and User Access Policies must have a description of more than 10 characters. Add a description of more than 10 characters to explain what access this metadata grants
MetadataDescriptionComponent 3 A component of the metadata types in the types property carries a description of more than 10 characters, so that a reader learns its purpose without reading everything that uses it. Add a description of more than 10 characters to explain what this component is for
MetadataDescriptionCore 3 A component of the data model, or a process on it, of the metadata types in the types property carries a description of more than 10 characters, so that a reader learns its purpose without reading everything that uses it. Add a description of more than 10 characters to explain what this part of the data model or process is for
MetadataDescriptionData 3 A data quality component of the metadata types in the types property, and each element named in the childElements property, carries a description of more than 10 characters, so that a reader learns what it checks and why. Add a description of more than 10 characters to explain what this data quality rule checks
MetadataDescriptionEinstein 3 An agent or Einstein component of the metadata types in the types property carries a description of more than 10 characters, so that a reader, and an agent that reads it, learns what the component does. Add a description of more than 10 characters to explain what this agent or Einstein component does
MetadataDescriptionIntegration 3 An integration component of the metadata types in the types property carries a description of more than 10 characters, so that a reader learns what it connects to and why. Add a description of more than 10 characters to explain what this integration component connects and why
MetadataDescriptionSharing 3 A sharing or access component of the metadata types in the types property, and every sharing rule in a SharingRules file, carries a description of more than 10 characters, so that a reader learns what access it grants or limits, and why. Add a description of more than 10 characters to explain what access this component grants or limits
MetadataDescriptionUserInterface 3 A user interface or code component of the metadata types in the types property carries a description of more than 10 characters, so that a reader learns what it does and where it is used. Add a description of more than 10 characters to explain what this user interface component does
MetadataDescriptionAnalytics 4 A report, dashboard, or other analytics asset of the metadata types in the types property carries a description of more than 10 characters, so that a reader learns what it shows and who uses it. Add a description of more than 10 characters to explain what this analytics asset shows
MetadataDescriptionStandardField 4 Every standard field should carry a description of more than 10 characters. The description of a standard field can be set through the API. Add a description of more than 10 characters to explain how this standard field is used here

Metadata Naming

Rule Priority What it does Message
MetadataNameStartsWithCapital 2 The API name of a Flow, a custom object, or a custom field starts with a capital letter, in a file of its own or inside an object file; the types property names the metadata types the rule reads. The name is the fullName element where the file has one, and the file's name otherwise. A name with a namespace prefix belongs to a managed package, and an org cannot change it, so it is not reported. A name is hard to change once the component is referenced, so the rule blocks. Start the API name with a capital letter
CheckboxFieldNamePrefix 3 The API name of a custom checkbox field starts with the prefix property, so that it reads as a yes-or-no question about the record, and never with the negativePrefix property, which would make every check of the field a double negative. Start this checkbox field name with ${prefix} and never with ${negativePrefix}, so that it reads as a yes-or-no question
MetadataLabelStartsWithCapital 3 The label of a Flow, a custom object, or a custom field starts with a capital letter or a digit, in a file of its own or inside an object file; the types property names the metadata types the rule reads. A label can be changed at any time, so the rule reports and does not block. Start the label with a capital letter

Metadata Quality

Rule Priority What it does Message
ProhibitedMetadataType 1 Tier 0 metadata is managed in each org and never enters version control. The rule reports every metadata file whose type is named in its metadataTypes property, and each organisation names its own types. This metadata type is Tier 0 (prohibited: ${metadataTypes}) and must not be committed to version control
BumpApiVersion 2 A metadata file at an API version below the floor must be raised. The minimumApiVersion property holds the floor, and each organisation sets its own. Notes on this rule. This metadata is below API version ${minimumApiVersion} - raise its API version
NoHardcodedWeblinkURLs 3 A weblink URL that holds the absolute URL of a Salesforce org is right in one org only, because every sandbox and every production org has a host name of its own. Use a relative URL. The allowedSubdomains property holds the subdomains of salesforce.com that may be written out, and each organisation adds its own. Weblinks from namespaced packages are skipped; an org cannot change them. Do not hard-code a Salesforce URL in a weblink - use a relative URL

Org Settings

Rule Priority What it does Message
NoListCustomSetting 2 No new list custom setting is created: org-wide values go in a custom metadata type, and user-specific or profile-specific values in a hierarchy custom setting. The rule reports each custom setting definition whose customSettingsType is List. A list custom setting is not created - keep org-wide values in a custom metadata type, and user-specific values in a hierarchy custom setting
FlowDeployAsActiveNotEnabled 3 The org must deploy processes and Flows as active: enableFlowDeployAsActiveEnabled in the Flow settings file is true. The rule reports the element when it is false. A settings file that leaves the element out is not reported. Deploy processes and flows as active is switched off - switch it on in Process Automation Settings
LightningWebSecurityNotEnabled 3 Lightning Web Security must be switched on, with its stricter Content Security Policy: lockerServiceNext and lockerServiceCSP in the session settings of the Security settings file are true. The rule reports either element when it is false. A settings file that leaves an element out is not reported. Switch on Lightning Web Security, with its stricter Content Security Policy - set lockerServiceNext and lockerServiceCSP to true in Session Settings

Permissions

Rule Priority What it does Message
ManageUsers 1 Manage Users is a restricted permission. Notes on this rule. Manage Users permissions should be restricted to the System Administrator profile
ModifyOrViewAllData 1 Profiles and Permission Sets must not grant ViewAllData or ModifyAllData. An integration user may need one of them; that grant is approved by senior reviewers. Notes on this rule. ViewAllData and ModifyAllData grant unrestricted record access - restrict to System Administrator only
ViewSetupPermissionSet 1 ViewSetup must not appear in Permission Sets. Notes on this rule. ViewSetup must not be granted via a Permission Set
ViewSetupProfile 1 ViewSetup is a restricted permission that must not appear in custom profiles. Notes on this rule. ViewSetup should be restricted to the System Administrator profile
ModifyOrViewAllRecordsCustom 2 Profiles and Permission Sets should not grant View All or Modify All Records on custom objects. Notes on this rule. Use sharing rules to grant record access rather than View All or Modify All on custom objects
ModifyOrViewAllRecordsStandard 2 Profiles and Permission Sets must not grant View All or Modify All Records on standard objects. Notes on this rule. View All or Modify All Records on standard objects bypasses the sharing model - blocking violation

Secrets in Fields

Rule Priority What it does Message
NoCredentialNamedField 2 A custom field whose name suggests a credential, on any object, standard or custom, and on any custom metadata type or custom setting, whatever its visibility: outside a managed package, a protected custom metadata type behaves like a public one, and a secret is kept in an External Credential. The rule reads the field's name, never its value, so a name such as Assigned_To__c is reported too and the owning team accepts the report. The credentialWords property holds the words. Written in Java. Notes on this rule. Do not keep a credential in a field of any object, custom metadata type, or custom setting: use an External Credential

Secrets in Metadata

Rule Priority What it does Message
AvoidHardcodedSecretSalesforce 2 A secret written into metadata is read by everyone who can read the repository, and it stays in the history after the file is corrected. Keep a secret in an External Credential or a Named Credential, and read it from there. The secretPattern property holds the form of one kind of secret; copy the rule once for each vendor. Remove this Salesforce session ID - keep secrets out of metadata

Sharing Model

Rule Priority What it does Message
NonPrivateCustomObjectSharing 1 Custom objects must use Private, or a platform-controlled sharing model (ControlledByParent and the other Controlled* values), for both the internal and the external org-wide default. Custom objects must use Private or a platform-controlled org-wide default sharing model
NonPrivateStandardObjectSharing 1 Standard objects must use Private, or a platform-controlled sharing model (ControlledByParent, ControlledByCampaign, ControlledByLeadOrContact), for both the internal and the external org-wide default. Standard objects must use Private or a platform-controlled org-wide default sharing model

Custom Rules Not Adopted

When this reference stops recommending a custom rule, the rule stays in its ruleset file, commented out, with a note that says why. It does not run, and an organisation can restore it.

Rule Language Why it is not recommended
MetadataRequiresApproval XML It reported every settings file and the types in its list, Audience and SharingSet. All of them are Tier 1 metadata, and the architect team reviews every Tier 1 change through CODEOWNERS and the Metadata Risk Tier workflow. So the rule repeated that review, and it blocked the gate until the change was approved. An organisation that wants a developer to see the warning in the editor, before the pull request, can restore it