The Goal
A validation rule should catch errors that cause real downstream problems. Data that would break a report, confuse a customer, fail an integration, or violate a business policy. It should tell the user what’s wrong and how to fix it.
Rules that just frustrate users — requiring fields nobody populates, blocking save on hypothetical edge cases, enforcing format preferences — are noise. They train users to ignore error messages.
This article separates the useful from the noise.
Rules Worth Writing
1. Required for Next Stage
A field required at a specific stage, not always.
AND(
ISPICKVAL(StageName, 'Proposal/Price Quote'),
ISBLANK(Amount)
)
“Amount is required when Stage is Proposal/Price Quote.” Doesn’t annoy users on new opportunities; catches missing data when it matters.
2. Numeric Range Enforcement
AND(
NOT(ISBLANK(Discount__c)),
OR(Discount__c < 0, Discount__c > 50)
)
“Discount must be between 0 and 50.” Prevents typos from creating $5000 discounts on $100 deals.
3. Date Sanity Checks
CloseDate < TODAY() - 365
“Close Date more than 1 year in the past requires manager override.” Catches rep typos.
4. Referential Integrity
AND(
ISPICKVAL(Type, 'Customer - Direct'),
ISBLANK(AccountId)
)
“Direct customers must have an Account selected.” Prevents orphan records that break reporting.
5. Cross-Object Consistency
AND(
Account.Industry = 'Healthcare',
ISBLANK(HIPAA_Contract_Number__c)
)
“Healthcare accounts must have a HIPAA contract number.” Keeps compliance data complete.
6. Status Transition Guards
AND(
ISCHANGED(Status__c),
ISPICKVAL(PRIORVALUE(Status__c), 'Closed'),
NOT($Permission.CanReopenClosed)
)
“Cannot reopen a closed record without permission.” Enforces workflow boundaries.
7. Formatting That Matters
Rules that normalize data critical for integrations:
NOT(REGEX(Phone, '^\\+?[0-9\\-\\s()]{7,}$'))
“Phone must be a valid format.” Useful when that phone feeds into a dialer.
Skip formatting rules that don’t matter — whether the first letter is capitalized, whether email is lowercase. Those are cosmetic.
8. Duplicate Prevention (When Dedup Rules Aren’t Enough)
For custom duplicate scenarios standard dedup doesn’t handle:
AND(
ISNEW(),
VLOOKUP(...existing record...) != null
)
Use sparingly — this pattern is expensive. Prefer Duplicate Rules for normal dedup.
Rules NOT Worth Writing
Every Field Required
Marking every field as required (via rule) generates constant friction. Users create records via data loaders, integrations, and quick actions where not every field is known. Required fields at the field level should be reserved for genuinely required data.
Over-Specific Format Policing
NOT(REGEX(FirstName, '^[A-Z][a-z]+$'))
“First name must be capitalized with no numbers.” Trips up legitimate names (Mary-Jane, O’Brien, 一郎) and annoys everyone.
Rules Duplicating Field Types
If Amount__c is a Currency field, validation for “must be numeric” is redundant — the field type enforces it.
Rules Caught By Record-Triggered Flow
If a before-save flow will populate the field automatically, a validation rule is redundant.
Rules Duplicated Across Many Fields
Fifteen rules each asserting “if Stage is Closed Won, field X is required” become maintenance pain. Build one rule or one flow that checks a list of required fields.
Vague Error Messages
Error Message: "Invalid data"
User has no idea what’s wrong. A good error message names the field, says what’s wrong, and suggests the fix.
Error Message: "Amount must be greater than zero when Stage is Qualification."
Error Message Standards
Every error message should:
- Name the field(s) involved.
- Describe what’s wrong in plain language.
- Suggest the fix — what the user should do.
Bad: “Invalid input.”
Better: “Amount is required.”
Best: “Amount is required when Stage is Qualification. Please enter an expected deal value.”
Organizing Many Rules
As rule counts grow, discipline matters.
Naming convention. VR_Opportunity_Amount_Required_At_Proposal. Prefix for type, then object, then what the rule checks. Grep-able.
Description field. Every rule has a Description. Use it. Explain why the rule exists and when it fires.
Active / Inactive flag. When retiring a rule, deactivate before deleting. Keep it inactive for one release cycle in case of rollback.
Change log. Track rule changes in a spreadsheet or in source control (DX metadata). Orgs with hundreds of rules accumulate drift otherwise.
Performance Considerations
Validation rules run on every save. Slow rules slow saves.
- Avoid rules that traverse deep relationship chains (
Account.Parent.Parent.Name). - Avoid rules with heavy text manipulation (large REGEX, nested FORMULAs).
- Test rules with bulk loads — a rule that works on single saves may time out on 200-record inserts.
Interaction With Automation
Order of execution: validation rules run after before-save flows. A before-save flow that sets a field’s value has its value evaluated by the validation rule.
Use this deliberately: a before-save flow can compute a value to be validated, ensuring validation runs against the final state, not user-provided input.
Record-triggered after-save flows run after validation. They can’t change validation outcomes.
Bypass Patterns
Legitimate reasons to bypass:
- Admin data loader cleanups.
- Integration imports for historical data.
- System-generated records from automation.
Common bypass pattern: a custom permission or a profile check.
AND(
/* rule condition */,
NOT($Permission.BypassValidationRules)
)
Grant the permission to integration users and admins during cleanups. Audit permission grants — they can be used maliciously to skip enforcement.
Testing
Validation rules are hard to unit test directly, but you can:
- Write Apex tests that create records violating each rule; assert they fail with the expected error.
- Maintain a list of “canary” records in sandbox that should trigger each rule.
- Include validation scenarios in UAT for any rule change.
Frequently Asked Questions
Can a validation rule reference a Custom Metadata value?
Yes — via $CustomMetadata merge syntax. Useful for config-driven thresholds.
Do validation rules fire on API inserts?
Yes. They fire on every save path except those explicitly configured to bypass (integration user permission).
What about upserts?
Yes — validation rules fire on upserts, same as inserts or updates.
Can I disable a validation rule for a specific Flow?
Not directly. The Flow runs as a user; validation applies. Grant the running user a bypass permission if legitimate.