Permission Metadata
Define access control with permission sets — object CRUD, field security, tab visibility, and row-level security
Permission Metadata
A Permission Set defines what a user can do within the application. It controls object-level CRUD operations, field-level visibility, tab/app access, and row-level security policies.
Basic Structure
const salesUserPermission = {
name: 'sales_user',
label: 'Sales User',
objects: {
account: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: false,
viewAllRecords: false,
modifyAllRecords: false,
},
opportunity: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: true,
viewAllRecords: false,
modifyAllRecords: false,
},
report: {
allowCreate: false,
allowRead: true,
allowEdit: false,
allowDelete: false,
viewAllRecords: true,
modifyAllRecords: false,
},
},
fields: {
'account.annual_revenue': { readable: true, editable: false },
'account.internal_notes': { readable: false, editable: false },
},
tabPermissions: {
crm: 'visible',
admin: 'hidden',
reports: 'default_on',
},
};Permission Set Properties
| Property | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Machine name (snake_case) |
label | string | optional | Display label |
isDefault | boolean | optional | [ADR-0090 D5] Baseline for the everyone position: an app-level default set is auto-bound at boot (guarded, idempotent); a package-shipped set instead becomes an install-time suggestion an admin confirms |
adminScope | AdminScope | optional | [ADR-0090 D12] Delegated-administration scope (BU subtree + assignable-set allowlist) |
objects | Record<string, ObjectPermission> | ✅ | Object-level permissions |
fields | Record<string, FieldPermission> | optional | Field-level security, keyed by 'object.field' |
systemPermissions | string[] | optional | System-level capabilities |
tabPermissions | Record<string, TabVisibility> | optional | Tab/app visibility |
rowLevelSecurity | RowLevelSecurityPolicy[] | optional | Row-level security policies |
Object Permissions
Control CRUD operations on each object:
objects: {
account: {
allowCreate: true, // Can create records
allowRead: true, // Can view records
allowEdit: true, // Can update records
allowDelete: false, // Can delete records
allowTransfer: false, // Can change record ownership
allowRestore: false, // Can restore deleted records
allowPurge: false, // Can permanently delete (GDPR)
viewAllRecords: false, // Bypass sharing rules for read
modifyAllRecords: false, // Bypass sharing rules for write
},
}| Permission | Description |
|---|---|
allowCreate | Create new records |
allowRead | View records (subject to sharing rules) |
allowEdit | Update records (subject to sharing rules) |
allowDelete | Soft-delete records |
allowTransfer | Transfer record ownership |
allowRestore | Restore records from trash |
allowPurge | Permanently delete records (GDPR compliance) |
viewAllRecords | View all records regardless of sharing rules |
modifyAllRecords | Edit all records regardless of sharing rules |
Field Permissions
Control visibility and editability of individual fields:
fields: {
// Keyed by 'object.field' → Permission
'account.annual_revenue': { readable: true, editable: false }, // Read-only
'account.internal_notes': { readable: false, editable: false }, // Hidden
'account.name': { readable: true, editable: true }, // Full access
'contact.ssn': { readable: false, editable: false }, // Restricted
'contact.salary': { readable: true, editable: false }, // Read-only
}| Permission | Description |
|---|---|
readable | User can see the field value |
editable | User can modify the field value |
Note: readable defaults to true and editable to false. The schema does not force editable: true to be paired with readable: true, but a field with readable: false is stripped from every read regardless of editable — always declare them together, and treat { readable: false } as completely hiding the field.
Tab Permissions
Control which apps/tabs are visible to users:
tabPermissions: {
crm: 'visible', // Shown
admin: 'hidden', // Hidden — removed from nav
reports: 'default_on', // Shown today (per-user toggle default not yet implemented)
analytics: 'default_off', // Shown today (per-user toggle default not yet implemented)
}| Visibility | Description |
|---|---|
visible | Shown (also the effective behavior when a tab is left unset) |
hidden | Hidden — the app is removed from the user's navigation |
default_on | Shown today; reserved for a future per-user toggle default |
default_off | Shown today; reserved for a future per-user toggle default |
Note: Only hidden currently affects runtime behavior — the app-visibility gate hides an app when its tab is set to hidden and treats every other value (including default_off) as visible. The per-user toggle / default-off semantics implied by default_on/default_off are not yet implemented.
System Permissions
Grant system-level capabilities:
systemPermissions: [
'manage_users',
'customize_application',
'view_all_data',
'modify_all_data',
'manage_sharing',
'export_data',
'api_access',
'manage_integrations',
]Row-Level Security
Define policies that restrict which records a user can access:
rowLevelSecurity: [
{
name: 'own_records_only',
object: 'opportunity',
operation: 'select',
using: "owner_id == current_user.id",
},
{
name: 'same_organization',
object: 'account',
operation: 'select',
using: "organization_id == current_user.organization_id",
},
]Context Variables
Note: A permission set cannot declare its own context values. The
contextVariables field was removed in ADR-0105 D11 (enforce-or-remove) because
the RLS compiler never read it. Conditions reference the built-in
current_user.* variables described below, plus any set a registered membership
resolver stages into ExecutionContext.rlsMembership (referenced as
field IN (current_user.<key>)); a constant belongs in the policy itself as a
literal (status = 'published').
The built-in context variables available in RLS using/check clauses include
current_user.id, current_user.organization_id, and current_user.positions
(ADR-0068). positions is a string array — the canonical assignment field
(D3 reserves the word "role"; there is no current_user.roles) — so test
membership with CEL: 'org_admin' in current_user.positions or
current_user.positions.exists(p, p == 'sales_manager'). The framework-seeded
built-in position names are platform_admin, org_owner, org_admin, and
org_member; everyone/guest are the implicit audience anchors (ADR-0090 D9).
(current_user.isPlatformAdmin is a derived, deprecated alias of
'platform_admin' in current_user.positions.)
Union semantics — no Profile tier (ADR-0090 D2)
There is no Profile concept: permission sets are the only capability
container and a user may hold any number of them. A user's effective
permissions = the union of every set reached — via positions, direct
grants, the everyone anchor's bindings, and the additive baseline
(member_default). Baseline access is authored on the everyone anchor (or
suggested by a package via isDefault: true); job functions are ordinary
sets bound to positions.
Complete Example
const salesManagerPermission = {
name: 'sales_manager',
label: 'Sales Manager',
objects: {
account: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: true,
allowTransfer: true,
allowRestore: true,
allowPurge: false,
viewAllRecords: true,
modifyAllRecords: false,
},
opportunity: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: true,
allowTransfer: true,
allowRestore: true,
allowPurge: false,
viewAllRecords: true,
modifyAllRecords: true,
},
contact: {
allowCreate: true,
allowRead: true,
allowEdit: true,
allowDelete: false,
viewAllRecords: true,
modifyAllRecords: false,
},
},
fields: {
'account.annual_revenue': { readable: true, editable: true },
'account.internal_rating': { readable: true, editable: true },
'contact.salary': { readable: true, editable: false },
},
tabPermissions: {
crm: 'visible',
reports: 'visible',
admin: 'hidden',
},
systemPermissions: [
'export_data',
'api_access',
],
rowLevelSecurity: [
{
name: 'org_isolation',
object: 'account',
operation: 'select',
using: "organization_id == current_user.organization_id",
},
],
};Related
- Object Metadata — Objects that permissions control access to
- Field Metadata — Fields that field permissions control
- App Metadata — Apps that tab permissions control visibility for