This guide walks through 50 declarative validation-rule formulas for enforcing data-quality constraints without code. Each entry below includes the full problem statement, the governor-limit and best-practice constraints it's testing, and a numbered approach to solving it — then you can open the same problem in ApexArena's browser-based Apex editor and get instant pass/fail feedback against real test cases.
Write a Validation Rule on Opportunity that prevents saving a record when the
Stage is "Closed Won" or "Closed Lost" but CloseDate is blank.
AND(OR(ISPICKVAL(...)), ISBLANK(...))
Write a Validation Rule on Lead that requires the Phone field
when Rating is "Hot".
Write a Validation Rule on Opportunity that prevents saving when
Amount is a negative number.
Write a Validation Rule on Opportunity that prevents setting
CloseDate to a date in the past when creating a new record.
Write a Validation Rule on Contact that validates the Email
field contains a proper email format (must contain @ and a dot).
NOT(REGEX(...)) with a basic email patternWrite a Validation Rule on Account that requires the custom field
Name_Change_Reason__c to be filled in whenever the Name
field is changed on an existing record.
ISCHANGED(Name) to detect the changeName_Change_Reason__c to be non-blankWrite a Validation Rule on Opportunity that prevents the custom field
Discount__c from exceeding 30% unless the record owner
has the Manager profile (simulate with a custom field
Is_Manager__c checkbox on User).
Owner.Is_Manager__c is trueWrite a Validation Rule on Lead that blocks saving when the
Email field is changed to match a known pattern that indicates a
temporary/test email (contains "+test" or ends with
"@example.com").
CONTAINS() and ENDS() functionsWrite a Validation Rule on Opportunity that requires the
Description field when Amount is greater than
$500,000.
Write a Validation Rule on Opportunity that prevents the
StageName from being moved backwards in the sales cycle.
Stage order: Prospecting → Qualification → Proposal → Negotiation → Closed Won
ISPICKVAL(PRIORVALUE(StageName), ...) to detect the previous stageWrite a Validation Rule on Contact that enforces a
10-digit US phone number format (XXX) XXX-XXXX using
REGEX().
(555) 555-5555Write a Validation Rule on Account that prevents an account from
referencing itself in the ParentId lookup field.
ParentId equals the account's own IdWrite a Validation Rule on Account that requires
BillingState when BillingCountry is
"United States" or "US".
Write a Validation Rule on Opportunity that requires
Probability to be at least 10% when the stage is
"Qualification" or later.
Write a Validation Rule on Case that prevents closing a case
(Status = "Closed") unless the Description field contains a
resolution note (is not blank).
Write a Validation Rule on a custom object Contract__c that ensures
End_Date__c is always after Start_Date__c.
Write a Validation Rule on Account that makes Industry
required only when creating a new account.
Write a Validation Rule on Opportunity that prevents changing the
OwnerId after the opportunity is Closed Won.
Write a Validation Rule on Account that validates
BillingPostalCode is a valid US ZIP code — either
5 digits (12345) or ZIP+4 (12345-6789).
Write a Validation Rule on a custom object Quote__c that prevents
saving when the rollup summary field Total_Line_Items__c exceeds
50 line items.
Write a Validation Rule on the Opportunity object that prevents
users from reducing the Amount field once it has been set.
The rule should fire (return TRUE to block save) when:
Amount is less than the previous valueAmount was not nullPRIORVALUE(field) — returns the field's value before the current editISNEW() — returns TRUE if the record is being created for the first timeWrite a Validation Rule on the Contact object that ensures
the Phone field (if not blank) matches the format (XXX) XXX-XXXX
where X is a digit.
Valid examples: (415) 555-1234, (800) 867-5309
The rule should fire (return TRUE) when Phone is not blank but does not match the expected pattern.
REGEX(text, pattern) — returns TRUE if text matches the regex patternNOT(REGEX(...)) — returns TRUE when it does NOT match (use in validation)Write a Validation Rule on Task that prevents creating a new Task with a
Due Date (ActivityDate) that's already in the past.
ActivityDate is earlier than today.AND(ISNEW(), ActivityDate < TODAY())
Write a Validation Rule on Case that prevents saving a Case with
Status = "Escalated" while Priority is still set to
"Low" — an escalated case should never remain flagged as low priority.
AND(ISPICKVAL(Status, "Escalated"), ISPICKVAL(Priority, "Low"))
An Opportunity has a custom Discount__c percentage field and
a custom Approval_Comments__c text field. Write a Validation Rule that requires
Approval_Comments__c to be filled in whenever Discount__c exceeds
20%.
Discount__c > 20.Approval_Comments__c is blank.AND(Discount__c > 20, ISBLANK(Approval_Comments__c))
Every problem above runs in a real in-browser Apex editor with instant pass/fail feedback and best-practice linting. No Salesforce org needed.
Create Free Account →A Case has a custom text field Escalation_Reason__c. Write a
Validation Rule that blocks saving whenever Status is set to
"Escalated" but Escalation_Reason__c is left blank.
AND(ISPICKVAL(Status, "Escalated"), ISBLANK(Escalation_Reason__c))
An Opportunity has a custom checkbox Locked__c. Write a
Validation Rule that blocks any edit to an Opportunity once it's been
marked locked — but still allows the record to be created in the first place (even if
created with the box already checked).
AND(Locked__c, NOT(ISNEW()))
Write a Validation Rule on Account that requires
BillingState to be a valid 2-letter code whenever
BillingCountryCode is "US".
BillingState is blank (a separate
"state required" rule would cover that case).BillingState doesn't match a 2-letter pattern.AND(ISPICKVAL(BillingCountryCode, "US"), NOT(ISBLANK(BillingState)), NOT(REGEX(BillingState, "[A-Za-z]{2}")))Write a Validation Rule on Case that prevents changing the
Type field once a Case has moved past its initial "New" status —
changing the Type mid-investigation would invalidate work already done under the original
classification.
AND(NOT(ISPICKVAL(Status, "New")), ISCHANGED(Type))
Write a Validation Rule on Contact that blocks saving when
both Phone and Email are blank — every Contact
must have at least one way to be reached.
AND(ISBLANK(Phone), ISBLANK(Email))
A custom Project__c object has Start_Date__c and
End_Date__c date fields. Write a Validation Rule that blocks saving whenever
both dates are filled in but End_Date__c is before
Start_Date__c.
AND(NOT(ISBLANK(Start_Date__c)), NOT(ISBLANK(End_Date__c)), End_Date__c < Start_Date__c)
Write a Validation Rule on Opportunity that blocks increasing
Amount by more than 20% once the deal has reached the
"Negotiation/Review" stage — a sudden large jump this late usually signals a
data-entry mistake rather than a legitimate scope change.
AND(ISPICKVAL(StageName, "Negotiation/Review"), NOT(ISNEW()), NOT(ISNULL(PRIORVALUE(Amount))), Amount > (PRIORVALUE(Amount) * 1.2))
Write a Validation Rule on Case that prevents closing a Case
(Status = "Closed") on a Saturday or Sunday, since support staff aren't
available to confirm the resolution over the weekend.
DATE(1900, 1, 7) was a Sunday. Since days-of-week repeat
every 7 days, MOD(TODAY() - DATE(1900, 1, 7), 7) gives a number from 0–6 that's
always the same for a given day of the week: 0 = Sunday, 6 =
Saturday.
AND(ISPICKVAL(Status, "Closed"), OR(MOD(TODAY() - DATE(1900,1,7), 7) = 0, MOD(TODAY() - DATE(1900,1,7), 7) = 6))
A Case has a custom text field Reopen_Reason__c. Write a
Validation Rule that requires Reopen_Reason__c to be filled in whenever a
previously-"Closed" Case's Status is changed to anything else.
PRIORVALUE() on a picklist field returns a special picklist-comparison value
that cannot be compared directly with = or checked with
ISPICKVAL(). Wrap it in TEXT() first —
TEXT(PRIORVALUE(Status)) = "Closed" — to safely compare it as a plain string.
AND(ISCHANGED(Status), TEXT(PRIORVALUE(Status)) = "Closed", NOT(ISPICKVAL(Status, "Closed")), ISBLANK(Reopen_Reason__c))
A Contact has a custom self-lookup field Manager__c. Write a
Validation Rule that blocks saving a Contact whose Manager__c points back to
itself.
Id = Manager__c
A custom Quote_Line__c object has List_Price_Override__c,
Standard_List_Price__c, and Override_Justification__c fields.
Write a Validation Rule that requires Override_Justification__c to be filled in
whenever a rep overrides the list price to something other than the standard price.
Override_Justification__c is blank.AND(NOT(ISBLANK(List_Price_Override__c)), List_Price_Override__c <> Standard_List_Price__c, ISBLANK(Override_Justification__c))
Write a Validation Rule on Lead that blocks saving any Lead whose
Email field is left blank. Sales needs every Lead to have a working email
address before it can enter the queue.
Email is blank, on both create and edit.Email value must save without error.ISBLANK(Email)
Write a Validation Rule on Account that prevents saving an
AnnualRevenue value of zero or less. A blank AnnualRevenue is fine
(it just means the data hasn't been collected yet) — only an explicit non-positive number
should be rejected.
AnnualRevenue is populated and is
<= 0.AnnualRevenue must never trigger the error.AND(NOT(ISBLANK(AnnualRevenue)), AnnualRevenue <= 0)
An Opportunity has a custom picklist field Loss_Reason__c.
Write a Validation Rule that blocks saving an Opportunity as "Closed Lost"
unless Loss_Reason__c has been filled in — management needs to know why every
deal was lost.
StageName is "Closed Lost".Loss_Reason__c is blank.Loss_Reason__c.AND(ISPICKVAL(StageName, "Closed Lost"), ISBLANK(Loss_Reason__c))
An Account has a custom picklist Account_Tier__c and a
custom lookup field Account_Manager__c (to User). Write a Validation Rule that
blocks saving an Account tiered as "VIP" unless it has an
Account_Manager__c assigned.
Account_Tier__c is "VIP".Account_Manager__c is blank.AND(ISPICKVAL(Account_Tier__c, "VIP"), ISBLANK(Account_Manager__c))
A custom Invoice__c object has a text field
Invoice_Number__c. A Validation Rule formula cannot query other Invoice records,
so it cannot detect a true cross-record duplicate — but it can enforce that
every Invoice Number is present and follows a consistent, predictable format
(INV- followed by exactly 5 digits, e.g. INV-00042). Keeping the
format consistent is what makes duplicate numbers easy to spot with a report or a
before-insert Apex/flow check elsewhere.
Invoice_Number__c is populated but does not
match the pattern INV-##### (5 digits).AND(NOT(ISBLANK(Invoice_Number__c)), NOT(REGEX(Invoice_Number__c, "INV-[0-9]{5}")))An Opportunity has a custom Discount_Percent__c number
field. Write a Validation Rule that blocks saving a discount above 30%
unless the current user is a Sales Manager (by Profile) or holds a custom permission
Approve_Large_Discounts (via a Custom Permission).
Discount_Percent__c > 30."Sales Manager".Approve_Large_Discounts custom permission, even on another Profile.AND(Discount_Percent__c > 30, NOT(ISPICKVAL($Profile.Name, "Sales Manager")), NOT($Permission.Approve_Large_Discounts))
Write a Validation Rule on Opportunity that locks the
Amount field once a deal is already Closed Won or Closed Lost — Finance has
reported deals with amounts changing weeks after close, throwing off reporting.
Amount has actually changed on this edit.AND(NOT(ISNEW()), ISCHANGED(Amount), OR(ISPICKVAL(StageName, "Closed Won"), ISPICKVAL(StageName, "Closed Lost")), PRIORVALUE(StageName) = StageName)
A custom Order__c object has a picklist Status__c and four
custom text fields for the shipping address: Shipping_Street__c,
Shipping_City__c, Shipping_Postal_Code__c, and
Shipping_Country__c. Write a Validation Rule that blocks moving
Status__c to "Submitted" unless the full shipping address has been
filled in.
Status__c is "Submitted".AND(ISPICKVAL(Status__c, "Submitted"), OR(ISBLANK(Shipping_Street__c), ISBLANK(Shipping_City__c), ISBLANK(Shipping_Postal_Code__c), ISBLANK(Shipping_Country__c)))
An Opportunity has a custom checkbox Executive_Approval__c.
Write a Validation Rule that blocks moving an Opportunity worth more than
$500,000 into "Negotiation/Review" or "Closed Won"
unless Executive_Approval__c has been checked.
Amount > 500000.Executive_Approval__c is unchecked.StageName is "Negotiation/Review" or
"Closed Won" — earlier stages are unaffected.AND(Amount > 500000, NOT(Executive_Approval__c), OR(ISPICKVAL(StageName, "Negotiation/Review"), ISPICKVAL(StageName, "Closed Won")))
Validation Rules cannot fire on delete in Salesforce — there is no delete
context for a Validation Rule to evaluate, so "prevent deleting an Account with active
Contracts" is not something a Validation Rule formula can implement directly (that requires a
before delete Apex trigger). The realistic, formula-based equivalent is
preventing an Account from being marked inactive while it still has an
active Contract — which a roll-up summary field can expose to a Validation Rule.
Assume Account has a custom picklist Account_Status__c and a
roll-up summary number field Active_Contract_Count__c that counts related
Contracts with Status = "Activated". Write a Validation Rule that blocks setting
Account_Status__c to "Inactive" while
Active_Contract_Count__c is greater than zero.
Account_Status__c is being set to
"Inactive".Active_Contract_Count__c > 0.AND(ISPICKVAL(Account_Status__c, "Inactive"), Active_Contract_Count__c > 0)
Account has a custom text field Tax_ID__c and uses the
standard BillingCountryCode field (ISO country picklist code, e.g.
"US", "CA", "GB"). Each country has a different Tax ID
format:
12-3456789RT, 4 digits — e.g.
123456789RT0001GB followed by 9 digits — e.g.
GB123456789Write a Validation Rule that blocks saving a populated Tax_ID__c that does not
match its country's expected format.
Tax_ID__c is populated — a blank Tax ID is a separate
concern, not handled by this rule.CASE() keyed off BillingCountryCode to pick the right
REGEX() pattern per country.AND(NOT(ISBLANK(Tax_ID__c)), CASE(BillingCountryCode, "US", NOT(REGEX(Tax_ID__c, "[0-9]{2}-[0-9]{7}")), "CA", NOT(REGEX(Tax_ID__c, "[0-9]{9}RT[0-9]{4}")), "GB", NOT(REGEX(Tax_ID__c, "GB[0-9]{9}")), NOT(REGEX(Tax_ID__c, ".+"))))A custom Subscription__c object has date fields
Subscription_Start_Date__c and Subscription_End_Date__c. Write a
Validation Rule that blocks saving a Subscription whose term (End Date minus Start Date)
exceeds 5 years (treated as 1826 days, accounting for a leap year in most
5-year spans).
AND(NOT(ISBLANK(Subscription_Start_Date__c)), NOT(ISBLANK(Subscription_End_Date__c)), (Subscription_End_Date__c - Subscription_Start_Date__c) > 1826)
This organization runs a custom fiscal year that starts
April 1 and ends March 31 of the following calendar year
(e.g. FY2027 runs from April 1, 2026 through March 31, 2027) — different from Salesforce's
built-in Fiscal Year settings. Write a Validation Rule on Opportunity that
confirms whether a given CloseDate falls inside the fiscal year that contains
it, by computing that fiscal year's own start and end dates directly from
CloseDate.
CloseDate's month is
January–March, the fiscal year started April 1 of the previous calendar
year; if April–December, it started April 1 of the same calendar year.CloseDate is not before its own fiscal year's
start or after its own fiscal year's end — which, by construction, is always true, so this
rule intentionally never blocks a save on its own. It exists to demonstrate the fiscal-year
boundary math as a reusable building block for other rules that compare
CloseDate against a different record's fiscal year (e.g. a Budget's
locked fiscal year field).DATE(YEAR(CloseDate) - IF(MONTH(CloseDate) < 4, 1, 0), 4, 1) /* fiscal year start */ DATE(YEAR(CloseDate) + IF(MONTH(CloseDate) < 4, 0, 1), 3, 31) /* fiscal year end */
Finance flagged a pattern of large deals being rushed to close without proper review.
Write a Validation Rule on Opportunity (with custom field
Deal_Desk_Review__c, a text field logging the Deal Desk reviewer's initials)
that blocks saving when all of the following are true at once:
Amount is greater than $250,000,StageName is "Negotiation/Review",CloseDate is less than 3 days away from today, andDeal_Desk_Review__c has not been filled in.Any deal missing even one of these four conditions (e.g. it's a smaller deal, or it's already been reviewed) must save without error.
AND() — this is a narrow, specific
guard, not a broad one.CloseDate - TODAY() gives the number of days until close; use it to test
the "fast-closing" condition.AND(Amount > 250000, ISPICKVAL(StageName, "Negotiation/Review"), (CloseDate - TODAY()) < 3, ISBLANK(Deal_Desk_Review__c))
Write and run real Apex code right in your browser — instant pass/fail feedback, best-practice linting, and governor limit monitoring. No Salesforce org needed.
Create Free Account → Explore All ProblemsApexArena is a free, browser-based Salesforce Apex coding practice platform covering every major topic tested on the Salesforce Platform Developer I (PD1) and Platform Developer II (PD2) certification exams. All problems run directly in your browser with instant pass/fail feedback, best-practice linting (SOQL in loops, DML in loops, empty catch blocks), and governor limit monitoring — no Salesforce Developer Edition org required.
Related tutorials: Apex Triggers · SOQL · Batch Apex · Interview Q&A · Governor Limits