Edit_Closed_Cases, add NOT($Permission.Edit_Closed_Cases) to the rule, and assign it to auditors via a permission set. No profile checks in formulas!
Imagine you have a rule: no one can discount an Opportunity by more than 20%. But the VP of Sales needs to override this rule when closing major deals.
The Old/Bad Way (hardcoding): Write a validation rule checking profile names: Discount__c > 0.20 && $Profile.Name != 'System Administrator' && $Profile.Name != 'VP of Sales'.
Why this is bad: If Regional Managers later need this capability, you must rewrite formula rules across the org. If a user changes roles, you must update their profile. It quickly becomes a maintenance nightmare.
The New/Good Way (Custom Permissions):
- Create a Custom Permission named
Can_Approve_Large_Discounts. - Create a Permission Set called "Discount Approvers" and add that Custom Permission to it.
- Assign the Permission Set to the VP of Sales — and anyone else who needs it.
- Write the validation rule to look for the capability, not the person:
Discount__c > 0.20 && NOT($Permission.Can_Approve_Large_Discounts).
The payoff: The system no longer cares who the user is or what profile they carry. It only checks if they hold the "VIP pass". Need a new manager to have access? Simply assign the Permission Set.
Concept
A Custom Permission is a named flag that you define, assign, and check. You can evaluate it in Apex, formulas, validation rules, and flows to decouple "can this user do X" from rigid profile name checks.
Assign Custom Permissions via Permission Sets and Permission Set Groups. Evaluate them declaratively using $Permission.MyPerm or in Apex using FeatureManagement.checkPermission('MyPerm'). It provides a maintainable answer to rule-override requirements without relying on brittle, hardcoded profile strings. Custom Permissions can also be shipped inside Managed Packages or tied to Connected Apps.
Core Q&A
A: Define a Custom Permission (e.g., Bypass_Amount_Limit). Assign it through a Permission Set to users permitted to bypass the rule, then write the Validation Rule formula to fire only when NOT($Permission.Bypass_Amount_Limit).
- Common Mistake: Hardcoding profile names in the formula. This breaks if a profile is renamed or cloned, and requires formula updates whenever new roles are onboarded.
- Custom Permissions travel across profiles seamlessly and produce a clean audit trail — whoever holds the permission set possesses the override.
- The exact same flag evaluates consistently in Apex via
FeatureManagement.checkPermissionand inside Flows. - Adding or removing an override user requires a simple Permission Set assignment with zero rule edits.
Follow-ups (Scenario-Based)
A1: Use a Custom Permission to gate a specific user capability. Assign it via permission sets and evaluate it in code or declarative tools — this is the standard answer for "which users can do X".
- Use a Hierarchy Custom Setting checkbox when an override requires a value or flag that resolves hierarchically at the Org, Profile, or User level (e.g., "skip all triggers for this integration user during data loads" via
$Setup). - Checking Profile Names: Avoid hardcoding profile names. It breaks on rename or clone, creates maintenance technical debt, and fails to scale.
A2: A Custom Permission provides a clean, user-agnostic flag. Automation and Apex code evaluate the permission directly, making feature activation a simple assignment task via Permission Sets shipped inside packages.
- Dark Deployments: Deploy underlying code inactive, gated behind an unassigned permission.
- Pilot Rollouts: Enable features for specific test groups purely through Permission Set assignments.
- Package Monetization: Gate premium managed package capabilities by binding Custom Permissions to Permission Set Licenses.
A: A Permission Set GRANTS access, while a Custom Permission is a specific FLAG inside it that your custom logic can TEST. Declarative tools like Validation Rules or Flows cannot query "does this user hold Permission Set X?", but they can check a Custom Permission in a single line (e.g., $Permission.Override_Delivery_Price). This decouples logic from packaging: the underlying permission flag can move across various permission sets without breaking the rule formula.
Recommended Reading: Master-Detail vs Lookup in Salesforce