---
title: "Bypassing automation with custom permissions"
url: "https://trailassembly.com/bypass-automation-custom-permissions/"
topic: "Flow and Automation"
published: "2026-09-21T11:45:29+00:00"
updated: "2026-09-23T10:53:36+00:00"
---

# Bypassing automation with custom permissions

**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:

```apex
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:

```apex
AND(
  NOT($Permission.Bypass_Account_Automation),
  ISCHANGED({!$Record.Status__c}),
  NOT(ISBLANK({!$Record.AccountId}))
)
```

## Checking it in Apex

```apex
public class AutomationBypass {
    public static Boolean isBypassed(String permissionName) {
        return FeatureManagement.checkPermission(permissionName);
    }
}
```

And in the handler, as the first thing it does:

```apex
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:

```apex
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.

```apex
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](https://trailassembly.com/downstream-dml-sprawl/) 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.

---

Source: https://trailassembly.com/bypass-automation-custom-permissions/
Trail Assembly — independent Salesforce write-ups.
