Permissions
Role-based access control defined in code.
Roles and permissions are defined in your cms.config.ts — not stored in the database. This keeps them version-controlled, reviewable, and deployable.
Defining roles
import { role } from "@cms/config";
// Full admin — bypasses all permission checks
const admin = role("admin", {
admin: true,
});
// Authenticated role with granular permissions
const editor = role("editor", {
permissions: {
posts: {
create: true,
read: true,
update: {
filter: { author: "$CURRENT_USER" },
fields: { exclude: ["status"] },
},
delete: false,
},
_media: {
create: true,
read: true,
update: true,
delete: false,
},
},
});
// Public role — unauthenticated API access
const publicRole = role("public", {
public: true,
permissions: {
posts: {
read: {
filter: { status: "published" },
fields: { exclude: ["internalNotes"] },
},
},
pages: {
read: {
filter: { status: "published" },
},
},
},
});
Permission rules
Each CRUD operation can be:
true— allowed with no restrictionsfalse— denied- An object with
filterand/orfields— allowed with constraints
{
create?: boolean | PermissionRule;
read?: boolean | PermissionRule;
update?: boolean | PermissionRule;
delete?: boolean | PermissionRule;
}
Item-level filters
Filters restrict which records the role can access. They’re appended as WHERE clauses automatically:
update: {
filter: { author: "$CURRENT_USER" },
}
A public request to GET /api/posts with filter: { status: "published" } automatically adds WHERE status = 'published'.
Field-level restrictions
Control which fields are readable or writable:
read: {
fields: { exclude: ["internalNotes", "draftContent"] },
}
Excluded fields are stripped from API responses and rejected in create/update requests.
Variables
Use variables in permission filters for dynamic values:
| Variable | Resolves to |
|---|---|
$CURRENT_USER | The authenticated user’s ID |
$CURRENT_ROLE | The authenticated user’s role name |
$NOW | Current ISO 8601 timestamp |
// Only allow editing posts you authored
update: {
filter: { author: "$CURRENT_USER" },
}
// Only show published items or items where the user is the author
read: {
filter: {
$or: [
{ status: "published" },
{ author: "$CURRENT_USER" },
],
},
}
How permissions are enforced
Permissions are applied at the query layer — every API request is scoped by the user’s role:
- CRUD booleans gate the entire operation. If
delete: false, the DELETE endpoint returns 403. - Item-level filters are merged into the query’s
WHEREclause. The user never sees records outside their filter scope. - Field-level restrictions control which columns appear in
SELECTand which are accepted inINSERT/UPDATE.
Admin roles (admin: true) bypass all checks.
Unauthenticated requests use the role marked public: true. If no public role exists, unauthenticated requests get 401.
Collections without permissions
If a collection is not mentioned in a role’s permissions, that role has no access to it. Permissions are deny-by-default.
Previous
Querying
Next
Hooks