apex-trigger-bypass-and-killswitch-patterns
Runtime mechanisms to disable Apex triggers without commenting out code: Custom Metadata kill switches via Trigger_Setting__mdt, Custom Permission gates via FeatureManagement.checkPermission, Hierarchy Custom Settings, and TriggerControl static-state bypass for nested operations. NOT for recursive-trigger-prevention (use apex-recursive-trigger-prevention) — bypass disables a handler intentionally; recursion guards prevent the same handler from re-entering itself.
Best use case
apex-trigger-bypass-and-killswitch-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Runtime mechanisms to disable Apex triggers without commenting out code: Custom Metadata kill switches via Trigger_Setting__mdt, Custom Permission gates via FeatureManagement.checkPermission, Hierarchy Custom Settings, and TriggerControl static-state bypass for nested operations. NOT for recursive-trigger-prevention (use apex-recursive-trigger-prevention) — bypass disables a handler intentionally; recursion guards prevent the same handler from re-entering itself.
Teams using apex-trigger-bypass-and-killswitch-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/apex-trigger-bypass-and-killswitch-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How apex-trigger-bypass-and-killswitch-patterns Compares
| Feature / Agent | apex-trigger-bypass-and-killswitch-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Runtime mechanisms to disable Apex triggers without commenting out code: Custom Metadata kill switches via Trigger_Setting__mdt, Custom Permission gates via FeatureManagement.checkPermission, Hierarchy Custom Settings, and TriggerControl static-state bypass for nested operations. NOT for recursive-trigger-prevention (use apex-recursive-trigger-prevention) — bypass disables a handler intentionally; recursion guards prevent the same handler from re-entering itself.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Apex Trigger Bypass And Killswitch Patterns
Activate when an Apex trigger needs to be disabled at runtime — for a data load,
a one-off support rerun, an integration user, or as a break-glass kill switch
during an incident — without commenting out code or hot-patching the org.
This skill is distinct from `apex-recursive-trigger-prevention`: recursion
prevention stops a handler from re-entering itself; bypass stops a handler
from running at all in a chosen scope.
## Why Bypasses Matter
- **Data loads and migrations.** Bulk inserts via Data Loader, ETL, or one-off
scripts often need triggers off so denormalisation, rollups, and outbound
callouts don't fire per row.
- **Integration users.** A nightly sync job from an external system has
already validated and enriched data — the org's enrichment triggers should
not re-run for that user.
- **Support reruns and backfills.** Replaying historical events should not
refire side-effecting integrations.
- **Incident response (kill switch).** When a trigger is implicated in an
outage, ops needs to disable it in seconds — without a deployment.
## Core Concepts
### 1. Custom Metadata kill switch (canonical)
`Trigger_Setting__mdt` records carry an `Is_Active__c` checkbox keyed by
`Object_API_Name__c` + `Handler_Class__c`. The repo's canonical `TriggerControl`
class (see `templates/apex/TriggerControl.cls`) reads this CMDT in a cached
SOQL on first call per transaction. Toggling `Is_Active__c` to `false` and
deploying the metadata change disables the handler org-wide. Custom Metadata
deploys faster than code and is auditable through the deployment record.
### 2. Custom Permission gate
`FeatureManagement.checkPermission('Bypass_Triggers')` is the canonical way to
ask "does the running user have the bypass capability?". The Custom Permission
is assigned via Permission Set, never directly to a profile. Integration users
get the perm set; admins do not (so accidental bypass during clicks is
impossible). The repo's `TriggerControl` already honours
`TriggerControl_BypassAll`.
### 3. Hierarchy Custom Setting toggle
A Hierarchy Custom Setting (`Trigger_Bypass__c` with checkbox fields per
handler) gives per-user, per-profile, and org-default override semantics.
Hierarchy lookups respect User → Profile → Org Default in that order. Useful
for short-lived overrides where a Permission Set assignment is too heavyweight,
but lacks the audit trail of Custom Metadata.
### 4. TriggerControl static-state bypass (in-transaction)
When Apex itself needs to suppress a downstream trigger during a cascade —
e.g. an Opportunity service that updates Accounts and does not want the
Account handler to re-derive territory — use programmatic
`TriggerControl.bypass('AccountTriggerHandler')` then `restore(...)` in a
`try/finally`. This is transaction-scoped static state; it does not survive
across transaction boundaries (Queueable, @future, Batch all start fresh).
### 5. Test guards
`Test.isRunningTest()` should NEVER short-circuit business logic. The right
pattern is to leave bypasses OFF in tests by default and have specific tests
opt in to the bypass via `TriggerControl.overrideForTest(...)`. Tests should
prove the trigger fires in the normal path.
## Why NEVER A Checkbox On User
Adding `Bypass_Triggers__c` to the `User` SObject sounds simple but is wrong:
- Users editing their own record can self-grant the bypass.
- There is no audit of who flipped the bit and when (User field history is
limited and noisy).
- It cannot be assigned through Permission Set Groups, breaking least-privilege
governance.
- It conflates identity (User) with capability (Permission), an
anti-pattern called out in Salesforce Well-Architected Security guidance.
Use a Custom Permission and assign it through a Permission Set instead.
## Governance — Every Bypass Must Be Auditable
Every code path that decides to bypass should write a single line to an
`Application_Log__c` (or platform event) — "skipped AccountTriggerHandler at
2026-04-28T14:02:11Z because TriggerControl_BypassAll permission is granted
to user 005...". Without this, post-incident forensics is impossible.
## Test Pattern
ALWAYS run trigger tests with no bypass applied unless the test is explicitly
proving the bypass works. Do this by clearing static state in
`@TestSetup` and asserting `TriggerControl.isActive(...) == true` before
the work.
## Recommended Workflow
1. Decide bypass scope: org-wide kill switch (CMDT), per-user/integration
(Custom Permission), per-profile or short-lived (Hierarchy CS), or
in-transaction cascade suppression (programmatic).
2. Add or update the `Trigger_Setting__mdt` record (or Custom Permission
assignment) — never edit the trigger code.
3. Wire the handler's `run()` entry point to call
`TriggerControl.isActive(sObjectName, handlerName)` and short-circuit.
4. For programmatic bypass during a cascade, wrap the inner DML in
`try { TriggerControl.bypass(...); ... } finally { TriggerControl.restore(...); }`.
5. Emit an `Application_Log__c` entry whenever a bypass actually fires.
6. Write tests that prove (a) the trigger runs by default, (b) the kill
switch disables it, and (c) restore() reverts the static-state bypass.
7. Document the kill-switch runbook entry — who can flip it, how to flip it,
how to verify, how to revert.
## Review Checklist
- [ ] Handler `run()` calls `TriggerControl.isActive(...)` first
- [ ] Custom Permission is `FeatureManagement.checkPermission`-gated, not a User field
- [ ] `Trigger_Setting__mdt` record exists for every handler
- [ ] Programmatic bypass uses `try/finally` to guarantee restore
- [ ] Bypass invocations write to `Application_Log__c`
- [ ] Tests run with bypass OFF by default
- [ ] No commented-out trigger code anywhere
- [ ] Runbook documents how to flip the kill switch and revert
## Salesforce-Specific Gotchas
1. **CMDT cache lag.** `Trigger_Setting__mdt` changes can take a few seconds
to propagate after deploy because of platform-side metadata caching.
Verify by hitting the handler from a fresh transaction.
2. **Static state dies at transaction boundaries.** A Queueable enqueued from
inside `TriggerControl.bypass(...)` runs in a NEW transaction with fresh
static state — bypass is gone.
3. **Hierarchy CS lookup order is User → Profile → Org Default.** Setting
only the org default leaves admin-test users bypassed unintentionally.
## Output Artifacts
| Artifact | Description |
|---|---|
| `Trigger_Setting__mdt` records | Per-handler kill switch toggles |
| `TriggerHandler.run()` override | Calls `TriggerControl.isActive(...)` first |
| Programmatic bypass blocks | `try/finally` wrappers around cascade DML |
| Bypass audit log entries | `Application_Log__c` rows on each bypass fire |
| Kill-switch runbook entry | Ops doc for incident response |
## Related Skills
- `apex/apex-recursive-trigger-prevention` — re-entry guards (different problem)
- `apex/apex-trigger-handler-framework` — the handler pattern being gated
- `apex/apex-application-logger` — the audit log destinationRelated Skills
mfa-enforcement-patterns
Design MFA enforcement: auto-enablement, Salesforce Authenticator rollout, exceptions, service accounts, API-only users, SSO interop, and audit. Trigger keywords: MFA, multi-factor, two-factor, Salesforce Authenticator, MFA exception, MFA SSO, api-only MFA. Does NOT cover: end-user password policies, device-trust posture, or non-Salesforce IdP configuration.
encrypted-field-query-patterns
Design SOQL, filters, reporting, and indexes against Shield Platform Encryption fields. Trigger keywords: Shield Platform Encryption, encrypted field query, probabilistic vs deterministic encryption, encrypted SOQL filter, encrypted field index. Does NOT cover: Classic Encryption (deprecated), field-level security policy, or tenant secret key rotation.
apex-managed-sharing-patterns
Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.
omnistudio-testing-patterns
Use when testing or validating OmniStudio components — OmniScript preview, Integration Procedure step debugging, DataRaptor field-mapping validation, and end-to-end UTAM-based automation. NOT for Apex unit testing or standard Flow debugging.
omnistudio-error-handling-patterns
Use when designing fault behavior across Integration Procedures, DataRaptors, OmniScripts, and FlexCards — error routing, user-facing messaging, retry semantics, and idempotency. Triggers: 'omnistudio error', 'integration procedure fault', 'dataraptor error handling', 'omniscript retry', 'flexcard action failure'. NOT for general Apex exception design or Flow fault paths.
omnistudio-ci-cd-patterns
Use when designing or implementing CI/CD pipelines for OmniStudio components — DataPack export/import, versioning, environment promotion, and automated deployment. NOT for standard Salesforce metadata CI/CD or Apex-only pipelines.
omniscript-design-patterns
Use when designing or reviewing OmniScripts for guided experiences, step structure, branching, save/resume, and the boundary between OmniScript, Integration Procedures, DataRaptors, and custom LWCs. Triggers: 'omniscript design', 'too many steps in omniscript', 'save and resume omniscript', 'branching in omniscript', 'when should this be an integration procedure'. NOT for deep Integration Procedure or DataRaptor design when the guided interaction layer is not the main concern.
integration-procedure-cacheable-patterns
Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers cache key design, TTL selection, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss/stale. Does NOT cover general IP authoring (see omnistudio-error-handling-patterns) or LWC client-side caching.
flexcard-design-patterns
Use when designing, building, or reviewing OmniStudio FlexCards — including data source selection, card states, actions, conditional visibility, flyout configuration, and child card iteration. Triggers: 'FlexCard', 'card template', 'flyout', 'card action', 'card state', 'data source', 'child card', 'conditional visibility'. NOT for OmniScript design, standalone LWC development, or Apex controller architecture outside the FlexCard context.
dataraptor-patterns
Use when designing or reviewing OmniStudio DataRaptors, especially Extract versus Turbo Extract versus Transform versus Load, field mapping strategy, performance tradeoffs, and when to move work into Integration Procedures or Apex. Triggers: 'DataRaptor Extract', 'Turbo Extract', 'DataRaptor Load', 'DataRaptor Transform', 'OmniStudio data mapping'. NOT for overall OmniScript journey design or Integration Procedure sequencing when the main question is not the DataRaptor shape itself.
wire-service-patterns
Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.
message-channel-patterns
Use when implementing Lightning Message Service (LMS) to enable cross-DOM communication between LWC, Aura, and Visualforce components on the same Lightning page, using message channels. Triggers: 'communicate between unrelated LWC components', 'send data between Visualforce and LWC', 'lightning message service not working', 'APPLICATION_SCOPE vs default scope', 'message channel metadata deployment'. NOT for parent-child component communication (use component-communication) or server-side events.