Access Control
Access rules are defined directly in each collection config file using helper functions. Rules are pure functions evaluated before every API operation. Any operation without a rule is allowed by default — except on auth collections, which are default-deny.
import { defineCollection, fields, hasRole } from "@kidecms/core";
export default defineCollection({ slug: "posts", access: { create: hasRole("admin", "editor"), update: hasRole("admin", "editor"), delete: hasRole("admin"), publish: hasRole("admin", "editor"), }, fields: { ... },});In this example, read is not set so anyone can read posts. Only the operations you want to restrict need a rule.
Auth collections
Section titled “Auth collections”A collection with auth: true holds the passwords and roles that every other rule is checked against, so default-allow would let any signed-in account promote itself to admin or overwrite someone else’s password. These collections are therefore default-deny:
| Operation | Without an explicit rule |
|---|---|
create, delete |
Admins only |
read, update |
Admins, or your own record |
publish, schedule |
Admins only |
Two fields are protected regardless of the collection rule, so loosening access.update can’t accidentally re-open privilege escalation:
role— only an admin can set it, including on their own record.password— an admin, or the owner of that record. This is what lets a non-admin change their own password. Create and update differ. On create, onlyroleis stripped for non-admins, so a new account can still set its initial password. On update, bothroleandpasswordare stripped for non-admins, unless a field-levelaccess.updaterule overrides.
Declaring your own rule replaces the default for that operation, and a field-level access.update rule on role or password replaces the field protection. Server-side code can still bypass everything with { _system: true }.
auth: true collections are part of Kide’s authentication boundary, not ordinary content. Do not pass untrusted input through _system, remove password hashing, or expose credential fields through custom serializers. See Authentication for the supported feature scope and evolution policy.
Helper functions
Section titled “Helper functions”| Helper | Description |
|---|---|
hasRole("admin") |
Checks if user has the specified role |
hasRole("admin", "editor") |
Accepts multiple roles (OR logic) |
Operations
Section titled “Operations”| Operation | When checked |
|---|---|
read |
find, findOne, findById, count |
create |
create |
update |
update |
delete |
delete |
publish |
publish, unpublish |
schedule |
schedule (falls back to publish rule) |
Context
Section titled “Context”({ user, doc, operation, collection }) => boolean | Promise<boolean>;Rules can be async — return a promise and the API awaits it.
user- current session user ({ id, role, email, ...customUserFields }ornull)doc- existing document. Forfindandcount,readrules are evaluated per document so rules can hide individual rows.operation- operation namecollection- collection slug
Field-level access
Section titled “Field-level access”Individual fields support read and update access rules:
summary: fields.text({ access: { read: hasRole("admin"), // hidden from non-admins update: hasRole("admin"), // read-only for non-admins },}),Both rules are enforced in the API, not just the admin UI:
read— the field is omitted entirely from API responses when the rule denies, and hidden from the admin UIupdate— denied values are stripped from create and update payloads before they reach the database; in the admin UI the field renders read-only and the existing value is preserved on save
Audit log
Section titled “Audit log”Kide writes an audit trail of admin actions to the cms_audit_log table, covering content, asset, user, invite, session, and password-reset events. Each entry records the action, the actor’s id, email, and role (or the attempted email on a failed login), and the request’s IP address and user agent. Every entry is also emitted as a structured log line (kind: "audit") so it reaches stdout-based log pipelines.
The activity feed in collaboration-enabled collections reads directly from this log. The cron tasks endpoint prunes entries older than 90 days on each run. To use a different retention, call pruneAuditLog(olderThanMs) from @kidecms/core yourself.