Overview

The ValidationRule metadata type (objects/<ObjectName>/validationRules/<RuleName>.validationRule-meta.xml) enforces declarative data quality standards before a record is saved to the database. A validation rule evaluates a boolean formula expression (errorConditionFormula) against the record being created or updated; when the formula returns true, Salesforce aborts the DML save and returns the configured errorMessage (both in the Lightning UI and as a FIELD_CUSTOM_VALIDATION_EXCEPTION in Apex and REST/Bulk APIs).

In Salesforce DX source format, validation rules are decomposed out of the parent CustomObject file into individual .validationRule-meta.xml files inside the object’s validationRules/ subfolder. This allows developers to toggle, version, and deploy individual validation rules without touching custom fields or object sharing settings.

Evaluates to FALSE (Valid) Evaluates to TRUE (Invalid) DML Insert / Update Before-Save Flows & Before Apex Triggers ValidationRule: errorConditionFormula Save Record to Database Abort Save & Throw FIELD_CUSTOM_VALIDATION_EXCEPTION

As shown in the execution flow above, ValidationRule formulas evaluate after Before-Save Flows and Before Apex Triggers have populated default field values, meaning any fields defaulted in a before insert trigger are visible to the validation rule.

Prerequisites

  • Permissions: “Customize Application” permission to deploy validation rules; “View Setup and Configuration” to query via Tooling API.
  • Referenced Fields: All fields, $CustomMetadata records, $Permission flags, or $Setup hierarchy custom settings referenced in errorConditionFormula must exist in the target org.
  • API Version: Verified against API version 68.0 (Winter ’27).

ValidationRule XML Elements

XML Element Type Required Description & Architectural Notes
fullName string ✅ Yes Rule API name (Require_Billing_Tax_Id). In package.xml, qualify with the object name (Account.Require_Billing_Tax_Id).
active boolean ✅ Yes Set to true to enforce at runtime, or false to disable without deleting.
errorConditionFormula string ✅ Yes Formula that returns true when data is invalid. Escape XML characters (&amp;, &lt;, &gt;, &apos;).
errorMessage string ✅ Yes User-facing message (≤255 characters) returned when the rule triggers.
errorDisplayField string No Field API name (Tax_Id__c) where the inline error attaches. Defaults to top of page if omitted.
description string No Documents the business owner and requirement behind the rule.

Tip

Never hardcode Profile.Name or User.Id in errorConditionFormula to bypass validation rules ($Profile.Name <> 'System Administrator'). Instead, check a Custom Permission (NOT($Permission.Bypass_Validation_Rules)) so you can grant temporary bypass access via a session-based PermissionSet.

Querying ValidationRule via Tooling API

The ValidationRule object is exposed through the Tooling API (sf data query --use-tooling-api), allowing you to audit active and inactive rules across your entire org.

1. List All Active Validation Rules on an Object (Tooling API)

Run this query with --use-tooling-api to inspect all validation rules on Account, Opportunity, or any custom object:

SOQL
SELECT Id, ValidationName, Active, EntityDefinition.QualifiedApiName,
       ErrorDisplayField, ErrorMessage, Description, LastModifiedDate
FROM ValidationRule
WHERE EntityDefinition.QualifiedApiName = 'Account'
ORDER BY Active DESC, ValidationName ASC

2. Find Inactive Validation Rules Left Behind in Production (Tooling API)

Inactive validation rules still count toward the per-object total validation rule limit (up to 500 total rules). Audit inactive rules with:

SOQL
SELECT ValidationName, EntityDefinition.QualifiedApiName,
       LastModifiedBy.Name, LastModifiedDate
FROM ValidationRule
WHERE Active = false
ORDER BY LastModifiedDate ASC

Example

The following Require_Tax_Id_On_Closed_Won.validationRule-meta.xml prevents closing an Invoice__c record as Paid unless ERP_External_Id__c is populated and Total_Amount__c is positive, while allowing ETL integrations with the Bypass_Invoice_Validations custom permission to bypass the rule.

force-app/main/default/objects/Invoice__c/validationRules/Require_ERP_Id_When_Paid.validationRule-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Require_ERP_Id_When_Paid</fullName>
    <active>true</active>
    <description>Prevents marking an Invoice as Paid without an ERP External ID and a positive amount, unless the user holds the Bypass_Invoice_Validations custom permission.</description>
    <errorConditionFormula>AND(
    NOT($Permission.Bypass_Invoice_Validations),
    ISPICKVAL(Status__c, &apos;Paid&apos;),
    OR(
        ISBLANK(ERP_External_Id__c),
        Total_Amount__c &lt;= 0
    )
)</errorConditionFormula>
    <errorDisplayField>ERP_External_Id__c</errorDisplayField>
    <errorMessage>An ERP External ID and a Total Amount greater than zero are required before marking an Invoice as Paid.</errorMessage>
</ValidationRule>

Retrieving and Deploying ValidationRules via package.xml and CLI

Because ValidationRule does not support <members>*</members>, specify each rule with its object prefix:

manifest/package.xml
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>Invoice__c.Require_ERP_Id_When_Paid</members>
        <members>Account.Require_Billing_Country</members>
        <name>ValidationRule</name>
    </types>
    <version>68.0</version>
</Package>

Deploy a single validation rule directly from its source path:

Terminal
sf project deploy start \
  --metadata ValidationRule:Invoice__c.Require_ERP_Id_When_Paid \
  --target-org Production

Limits and Gotchas

  • Active Validation Rules per Object: Enterprise Edition allows up to 100 active validation rules per object; Unlimited and Performance Editions allow up to 500 active validation rules per object.
  • Formula Size Limit: Maximum of 3,900 characters and 15,000 bytes when compiled (including referenced formula fields).
  • Compound Fields Not Supported: You cannot reference compound fields (BillingAddress, Name on Contact/Lead) directly in errorConditionFormula; reference their individual constituent fields (BillingStreet, BillingCountry, FirstName, LastName) instead.
  • VLOOKUP Function Restrictions: The VLOOKUP formula function is only supported in ValidationRule formulas (not in custom formula fields) and can only match against the Name field of a custom object.

Frequently asked questions

When do ValidationRules run in the Salesforce Order of Execution?

Custom validation rules execute immediately after System Validation and Before-Save Record-Triggered Flows and Before Apex Triggers, but before records are saved to the database or After Triggers run.

Does ValidationRule support the asterisk wildcard in package.xml?

No. ValidationRule does not support the wildcard (*) character in package.xml. You must either list each object-qualified rule name explicitly (such as Account.Require_Tax_Id) or retrieve the parent CustomObject.

How do I build a clean admin or data-migration bypass into a ValidationRule?

Create a CustomPermission (such as Bypass_Validation_Rules) and include NOT($Permission.Bypass_Validation_Rules) inside your errorConditionFormula. Assign that CustomPermission via a PermissionSet during migrations.

How do I query all active ValidationRules across my org using SOQL?

Use the Tooling API (--use-tooling-api) to query the ValidationRule object: SELECT ValidationName, Active, EntityDefinition.QualifiedApiName, ErrorDisplayField, ErrorMessage FROM ValidationRule WHERE Active = true.

Can I create a ValidationRule on a Custom Metadata Type (__mdt)?

Yes. Starting in API version 40.0, Custom Metadata Types support ValidationRules to validate configuration records during deployment or Setup editing.

What happens if the field specified in errorDisplayField is removed from the Page Layout?

If errorDisplayField is omitted or the specified field is not present on the user's Page Layout or Dynamic Form, Salesforce automatically displays the error message at the top of the page.

Comments

Questions, corrections and tips are welcome. Comments are GitHub Discussions, so you sign in with GitHub.