Salesforce validation rules are the workhorse of declarative data integrity. They stop bad data at the source, run on every insert and update, and (the part people forget) apply to API integrations exactly as they do to UI users. Here are 12 patterns that cover roughly 80% of what you will actually need, plus the formula tricks that keep them maintainable.
How validation rules work
A validation rule is a formula that returns a Boolean, and TRUE blocks the save. When it returns TRUE, Salesforce shows the error message you configured, either inline next to a field or at the top of the page, and the record does not save. Rules run between the BEFORE triggers and the database commit, on every insert and update, including bulk operations and API calls.
Three things to know:
- There is no warning mode. If the formula returns TRUE the save fails, and standard Salesforce gives you no soft-warning option.
- They run on every save. Even if no field changed, the rule still evaluates. Use
ISCHANGED()to scope a rule to actual changes. - They apply to the API too. Data Loader, Apex DML and external integrations all hit the same rules. That is usually what you want, and it is also why a sloppy rule breaks the overnight ETL job.
12 copy-paste validation rule examples
1. Email regex
Block invalid email format on a custom email field:
NOT(REGEX(Email_Address__c, "^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9.-]+$"))
Works for any standard regex; the ^ and $ anchors are required to prevent partial matches.
2. Required only when another field is set
Phone is required but only if Lead Status is "Qualified":
AND(
ISPICKVAL(Status, "Qualified"),
ISBLANK(Phone)
)
3. Block close date in the past
AND(
ISCHANGED(CloseDate),
CloseDate < TODAY()
)
The ISCHANGED guard means existing records with old past dates can still be saved. The rule only fires when CloseDate is being changed.
4. Discount cap by profile
Block discount > 20% unless the user is a System Administrator:
AND(
Discount__c > 0.2,
$Profile.Name <> "System Administrator"
)
5. Custom-permission override
Cleaner version of the above using a custom permission rather than profile name:
AND(
Discount__c > 0.2,
NOT($Permission.High_Discount_Override__c)
)
Custom permissions survive profile renames and let you grant the override to multiple profiles via permission sets.
6. Account-industry cross-object check
On Contacts, require Title when Account.Industry is "Technology":
AND(
TEXT(Account.Industry) = "Technology",
ISBLANK(Title)
)
7. Renewal vs new business
Different revenue rules for renewals:
AND(
IsRenewal__c = TRUE,
Amount < PRIORVALUE(Amount) * 0.9
)
PRIORVALUE returns the value before this save, which is what makes "amount cannot decrease by more than 10%" patterns possible.
8. Phone number length
Require US phones to be exactly 10 digits:
AND(
TEXT(Country__c) = "US",
NOT(REGEX(Phone, "^\\d{10}$"))
)
9. Conditional read-only after stage
Block edits to Amount once Opportunity reaches "Closed Won":
AND(
ISPICKVAL(StageName, "Closed Won"),
ISCHANGED(Amount)
)
This is the standard pattern for "freeze fields after stage X."
10. Date range on creation only
Birthday must be between 1900 and today, only enforced on insert:
AND(
ISNEW(),
OR(Birthday__c < DATE(1900, 1, 1), Birthday__c > TODAY())
)
ISNEW() returns TRUE only on insert, so the rule doesn't fire on subsequent updates.
11. Multi-select picklist requires specific value
Require Tags to include "VIP" when Annual Revenue exceeds $1M:
AND(
AnnualRevenue > 1000000,
NOT(INCLUDES(Tags__c, "VIP"))
)
12. Block edits on locked records
Prevent any edit when a checkbox is set:
AND(
Locked__c = TRUE,
$Profile.Name <> "System Administrator"
)
Best practices
Keep each rule narrow. One rule, one business intent. Combining "phone required AND email format AND amount cap" into a single mega-rule is unmaintainable, and you gain nothing by it: they fire in any order anyway.
Use ISNEW() and ISCHANGED() guards. Without them your rule fires on every save of every record forever, including legacy data and integrations updating unrelated fields. The ETL job at 3am should not be blocked by a rule that was only ever meant for user-initiated changes.
Reference custom permissions rather than profile names. $Profile.Name = 'Sales Manager' breaks the moment HR renames the profile. $Permission.Custom_Sales_Override__c survives the rename.
Write the error message from the user's point of view. Bad: "Validation rule X1 failed." Good: "Discount over 20% requires manager approval. Talk to your manager or use a Permission Set."
Test with a real bulk insert. A rule that is fine for one record can choke an integration loading 200 leads at once. Run it through Data Loader before you deploy it.
When not to use a validation rule
- Cleaning up existing records. Validation rules do not fix bad data that is already there, they only block future writes. Run a Data Loader update after deploying the rule, or write a one-shot Flow.
- Complex multi-record logic. A validation rule sees only the record being saved. To enforce "no two Opportunities for the same Account in 30 days", use a Trigger or a Flow with a Get Records.
- Warnings. There is no warning mode. If you need a "yes/no, are you sure?" prompt, use a Flow with a screen component.
Validation rules are declarative, instant, and impossible for an integration to skip, which is why I reach for them before anything else. The 12 patterns above cover most of the data integrity work you will meet. Pair them with Salesforce Flow best practices for the cases where validation alone is not enough.
Leave a Comment