Skip to content

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 "@/cms/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.

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, only role is stripped for non-admins (so a new account may set its initial password); on update, both role and password are stripped for non-admins, unless a field-level access.update rule 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 Description
hasRole("admin") Checks if user has the specified role
hasRole("admin", "editor") Accepts multiple roles (OR logic)

access.ts still exists but is auto-built from collection configs. You should not edit it directly.

Operation When checked
read find, findOne, findById, count
create create
update update
delete delete
publish publish, unpublish
schedule schedule (falls back to publish rule)
({ user, doc, operation, collection }) => boolean;
  • user - current session user ({ id, role, email, ...customUserFields } or null)
  • doc - existing document. For find and count, read rules are evaluated per document so rules can hide individual rows.
  • operation - operation name
  • collection - collection slug

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
},
}),
  • read hides the field entirely from the admin UI
  • update makes the field read-only in the admin UI; values are silently preserved on save