Create a custom permission, check it in every flow’s entry criteria and every trigger handler’s guard, and assign it via a permission set to the integration user or the person running a data load. That turns “disable the automation, run the load, remember to turn it back on” into a permission you grant and revoke, which is auditable and does not depend on anybody remembering.
Why the alternatives are worse
| Approach | Why it fails |
|---|---|
| Deactivate the flow during the load | Manual, org-wide, and forgotten at least once a year |
| Custom setting checkbox | Org-wide or per-user, but not deployable as permission metadata and easy to leave set |
| Check the running user’s Id in code | Hardcoded Ids that break on every sandbox refresh |
| Check the profile name | Breaks when profiles are renamed or consolidated |
| Custom permission | Deployable, assignable, revocable, visible in Setup |
The decisive advantage is that a custom permission is ordinary permission metadata. It deploys with the rest of your source, it shows up when somebody audits who can do what, and removing the permission set removes the bypass everywhere at once.
Setting it up
Setup → Custom Permissions → New. Give it a label and a name that says what it bypasses — Bypass_Account_Automation rather than Data_Load, because in two years nobody will remember what the load was.
Create one permission set per bypass and assign it to the users or integration accounts that need it. Resist the urge to build a single Bypass_Everything permission: the point is to turn off a specific, named thing.
Checking it in Flow
In a record-triggered flow, put the check in the entry criteria, not in a Decision element. Entry criteria that fail cost nothing. A flow that starts and then decides has already spent CPU on every record in the batch.
Use a formula resource in the entry condition:
NOT($Permission.Bypass_Account_Automation)
$Permission is available in flow formulas and resolves to true when the running user has the custom permission through any assigned permission set. Combine it with your real criteria:
AND(
NOT($Permission.Bypass_Account_Automation),
ISCHANGED({!$Record.Status__c}),
NOT(ISBLANK({!$Record.AccountId}))
)
Checking it in Apex
public class AutomationBypass {
public static Boolean isBypassed(String permissionName) {
return FeatureManagement.checkPermission(permissionName);
}
}
And in the handler, as the first thing it does:
public class AccountTriggerHandler {
public void beforeUpdate(List<Account> newList, Map<Id, Account> oldMap) {
if (AutomationBypass.isBypassed('Bypass_Account_Automation')) { return; }
// real work
}
}
FeatureManagement.checkPermission does not consume a SOQL query, which matters when the guard is evaluated on every trigger invocation. Querying SetupEntityAccess yourself to answer the same question is a query you do not need to spend.
Checking it in validation rules
The same $Permission global works in validation rule formulas, and this is where it earns its keep during migrations:
AND(
NOT($Permission.Bypass_Account_Validation),
ISBLANK(Industry),
ISPICKVAL(Type, 'Customer')
)
A historical data load that cannot satisfy today’s validation rules is otherwise a choice between deactivating the rules org-wide and cleaning data you were told to load as-is.
The risk, stated plainly
You are building a switch that turns off your business rules. Three things keep that from becoming a liability:
- Granularity. One permission per object or per rule family. A single global bypass will end up assigned to somebody permanently.
- Assignment hygiene. Grant through a permission set that is assigned for the duration of the load and removed afterwards. Permission set group expiration, where available to you, makes that automatic.
- Visibility. Review assignments periodically. A bypass assigned two years ago to a person who has since changed roles is the exact failure this is meant to prevent.
SELECT Assignee.Name, PermissionSet.Name
FROM PermissionSetAssignment
WHERE PermissionSet.Name LIKE 'Bypass%'
Run that query quarterly. It takes a minute and it is the whole governance story.
Where this pattern does not help
It does not bypass roll-up summary recalculation, master-detail parent locking, or the platform’s own order of execution — those are not automation you control. And it does not make a slow load fast if the cost is in downstream writes from automation that has no bypass check. Adding the check to three flows out of nine gets you three flows’ worth of saving.
Being systematic is what makes this work: every automation on the object gets the guard, added at the same time, or the mechanism is unreliable in a way that is worse than not having it.
Frequently asked
Does $Permission work in all formula contexts?
It works in validation rules, flow formulas and formula fields. It is evaluated for the running user, which is the point. It is not a global switch.
Does checkPermission cost a SOQL query?
No. FeatureManagement.checkPermission is the supported way to test a custom permission from Apex without spending a query, which is why it belongs in a trigger guard.
Should the integration user have the bypass permanently?
Often yes, and that should be a deliberate, documented decision rather than a side effect. If an integration must not run your automation, say so in the design and keep the assignment visible.
What about bypassing for a single record rather than a user?
That is a different mechanism. Usually a transient checkbox or a static flag in Apex for recursion control. Custom permissions answer “who”, not “which record”.
Can I use this to skip validation during a sandbox seed?
Yes, and it is one of the better uses. Grant the bypass to the seeding user, load, then remove it, and you never have to deactivate a rule org-wide.
Reference: Custom Permissions and Permission Sets, Salesforce Help; FeatureManagement Class, Salesforce Apex Reference Guide.