---
name: apex-custom-permissions-check
description: "Custom Permissions in Apex: FeatureManagement.checkPermission, $Permission global variable, permission-set gating of feature code, Custom Permission metadata. NOT for CRUD/FLS enforcement (use security-apex-crud-fls). NOT for standard Salesforce permissions (use permission-set-architecture)."
category: apex
salesforce-version: "Spring '25+"
well-architected-pillars:
- Security
- Operational Excellence
tags:
- apex
- custom-permissions
- featuremanagement
- permission-sets
- authorization
triggers:
- "featuremanagement.checkpermission apex custom permission"
- "gate apex feature behind custom permission"
- "custom permission vs profile permission best practice"
- "how to check custom permission in lwc and apex"
- "custom permission in validation rule and formula"
- "feature flag pattern salesforce custom permission"
inputs:
- Feature or code path to gate
- Target user populations (profiles / permission sets)
- Test scenarios needing permission toggling
outputs:
- Custom Permission metadata
- Apex check pattern
- Permission Set assignment plan
- Test class using System.runAs with permission set
dependencies: []
version: 1.0.0
author: Pranav Nagrecha
updated: 2026-04-21
---
# Apex Custom Permissions Check
Activate when gating code paths, feature availability, or field-level behavior behind a Custom Permission. Custom Permissions are the platform's feature-flag primitive — declarative on/off tokens assigned via Permission Sets or Profiles that Apex, LWC, formulas, and validation rules can check consistently.
## Before Starting
- **Create the Custom Permission metadata first.** Setup → Custom Permissions or via `-meta.xml` deployment.
- **Assign via Permission Set, not Profile** for maintainability.
- **Use `FeatureManagement.checkPermission`** for runtime checks — not the deprecated Schema describe path.
## Core Concepts
### Custom Permission metadata
```
false
```
API Name is the handle used in Apex / formulas.
### Apex check
```
Boolean canApprove = FeatureManagement.checkPermission('Approve_Big_Deals');
if (!canApprove) throw new AuraHandledException('Not authorized');
```
Per-user, transaction-cached. Safe to call repeatedly.
### Formula / validation rule
`$Permission.Approve_Big_Deals` returns Boolean for declarative gating.
### LWC
```
import hasApprove from '@salesforce/customPermission/Approve_Big_Deals';
```
Returns Boolean at module load — evaluate in getters.
### Testing
```
@IsTest
static void canApprove() {
User u = TestUserFactory.makeUser();
insert new PermissionSetAssignment(
AssigneeId = u.Id,
PermissionSetId = [SELECT Id FROM PermissionSet WHERE Name = 'Big_Deal_Approvers'].Id
);
System.runAs(u) { ... }
}
```
## Common Patterns
### Pattern: Gate Apex service entry point
```
public with sharing class ApprovalService {
public void approve(Id oppId) {
if (!FeatureManagement.checkPermission('Approve_Big_Deals')) {
throw new AuraHandledException('Not authorized');
}
}
}
```
### Pattern: LWC conditional render
```
import hasApprove from '@salesforce/customPermission/Approve_Big_Deals';
export default class Approve extends LightningElement {
get showButton() { return hasApprove; }
}
```
### Pattern: Validation rule gate
`AND(ISCHANGED(Status), Status='Approved', NOT($Permission.Approve_Big_Deals))`
## Decision Guidance
| Need | Mechanism |
|---|---|
| Feature flag for code path | Custom Permission |
| Object CRUD | Object permission on Permission Set |
| Field-level gate | FLS on Permission Set |
| Temporary admin toggle | Custom Setting + check |
| A/B test | Custom Permission + assignment script |
## Recommended Workflow
1. Define the feature boundary — what code runs for privileged users only.
2. Create the Custom Permission metadata.
3. Create a Permission Set granting it; assign to users.
4. Add `FeatureManagement.checkPermission` at service entry points.
5. Mirror the check in LWC and formulas where needed.
6. Write Apex tests using `System.runAs` + PermissionSetAssignment.
7. Document the permission in the feature runbook.
## Review Checklist
- [ ] Custom Permission metadata exists with clear label
- [ ] Assigned via Permission Set (not Profile)
- [ ] Apex uses `FeatureManagement.checkPermission`
- [ ] LWC uses `@salesforce/customPermission/` import
- [ ] Formulas use `$Permission.`
- [ ] Tests cover both granted and denied
- [ ] No hardcoded user Ids in permission checks
- [ ] Permission documented in feature doc
## Salesforce-Specific Gotchas
1. **`isLicensed=true`** requires the user's managed-package license; non-licensed users cannot receive it even if assigned.
2. **`Schema.describe` for Custom Permissions is deprecated** — use `FeatureManagement.checkPermission`.
3. **Custom Permission grants propagate through Permission Set Groups** — audit muting flags on groups.
## Output Artifacts
| Artifact | Description |
|---|---|
| Custom Permission metadata | XML file |
| Permission Set granting the CP | Metadata + assignment plan |
| Apex test class | Positive + negative cases with runAs |
## Related Skills
- `security/permission-set-architecture` — permission-set design
- `security/security-apex-crud-fls` — CRUD/FLS enforcement
- `lwc/lwc-user-permission-aware-components` — LWC permission checks