Overview
How Dyrected decides who can read and write your data — the rules you attach to collections, globals, and fields.
Access control is how you decide who can do what with your data. In Dyrected you write that as small rules attached directly to a collection, a global, or a field. There is no separate permissions table and no global role registry to keep in sync — the rule lives next to the thing it protects, and Dyrected runs it on the server for every request.
This page is the model: what a rule is, the shapes it can take, when it runs, and what it can see. The pages that follow show where to put rules for collections, globals, and fields. If you are deploying to Dyrected Cloud, also read Dyrected Cloud access control for the Cloud-specific policy limits and built-in policy names.
In Cloud, treat access rules as content rules that must sync to the hosted content backend. Use booleans, Jexl strings, and serializable named policies. Function rules are self-hosted-only because Cloud does not run arbitrary application code from your schema.
The mental model
An access rule answers one question: should this request be allowed? Dyrected evaluates the rule on the server, before it touches the database, and reads the result in three ways:
- A truthy result allows the operation.
- A
falseresult denies it — Dyrected responds with403 Forbidden. - An object result allows the operation but scopes it to matching documents — a row-level filter, covered in Collections.
A denied request always comes back in the same shape, so your frontend can detect it reliably:
{ "error": true, "message": "Access denied: read on posts" }Because the check runs before the database call, a denied read never loads the document and a denied write never persists anything.
The rule shapes
In Cloud, every access rule you sync should be serializable. That means booleans, Jexl strings, or named policies backed by Cloud built-ins, string policies, or boolean policies.
access: {
read: true, // 1. boolean
create: "'admin' in user.roles", // 2. Jexl string
delete: { policy: 'isAdmin' }, // 3. named policy
}Pick the shape by how much the rule needs to express:
| Shape | Reach for it when | Runs on |
|---|---|---|
| Boolean | The answer never changes — always open or always closed. | Cloud backend |
| Jexl string | A serializable check like a role test or field comparison. | Cloud backend |
| Named policy | You want a reusable rule and the policy is a Cloud built-in, string policy, or boolean policy. | Cloud backend |
Booleans and Jexl strings cover most Cloud rules. Reach for named policies when the same rule repeats or the policy name makes the intent clearer.
Jexl strings
A Jexl string is a small expression that Dyrected evaluates on the server. It reads like JavaScript comparison syntax, but it is a restricted, sandboxed language: it cannot call functions, import anything, or reach outside the values you hand it — which is exactly what makes it safe to store and sync to Cloud.
Your rule has the access context in scope (user, req, doc, data, id), plus these building blocks:
| You want to | Write | Example |
|---|---|---|
| Compare values | == != > >= < <= | doc.status == 'published' |
| Combine conditions | && || ! | user != null && !doc.locked |
| Check membership | in | 'admin' in user.roles |
| Read a property | . or ['key'] | user.roles, doc['status'] |
| Pick between values | cond ? a : b | user ? true : false |
Two things to remember: use == and != (not ===), and Dyrected registers no custom Jexl functions or transforms, so stick to the operators above.
A rule that evaluates to a boolean allows or denies:
access: {
read: "user != null", // any signed-in user
update: "'admin' in user.roles || 'editor' in user.roles",
}A rule that evaluates to an object becomes a row-level where filter — "{ owner: { equals: user.sub } }" limits the operation to the caller's own documents (covered in Collections). A ternary lets one rule return either shape, so it can hand signed-in users everything and scope everyone else:
access: {
// signed-in users see everything; anonymous readers only see published docs
read: "user != null ? true : { status: { equals: 'published' } }",
}For the complete expression syntax, see the Jexl reference.
Named policies
When the same rule shows up on several collections, define it once under accessPolicies and reference it by name.
In Dyrected Cloud, string and boolean policies sync as part of the site schema. Cloud also provides built-in policies such as isAuthenticated, hasRole, isOwner, and createdByCurrentUser.
export default defineConfig({
accessPolicies: {
isAdmin: "'admin' in user.roles",
},
collections: [
defineCollection({
slug: 'posts',
access: {
create: { policy: 'isAdmin' },
delete: { policy: 'isAdmin' },
},
fields: [{ name: 'title', label: 'Title', type: 'text' }],
}),
],
globals: [],
})String policies are inlined when the schema is sent to the admin panel, so a field or collection rule that references one stays reactive in the edit form, exactly like an inline Jexl rule.
If you need a policy with parameters, use one of the Cloud built-ins. For the current built-in policy list and common parameters, see Dyrected Cloud access control.
The same rule, two Cloud-safe ways
These rules are equivalent — each one says "a user can only touch their own documents." The result is identical; the shapes differ in how reusable they are.
Serializable and Cloud-safe — a good default.
access: {
read: "{ owner: { equals: user.sub } }",
}Defined once, referenced everywhere. A string policy is inlined for the admin panel and can sync to Cloud.
// defineConfig({ accessPolicies: { ownDocs: "{ owner: { equals: user.sub } }" } })
access: {
read: { policy: 'ownDocs' },
}What a rule can see
Every rule is evaluated with this context in scope:
| Variable | What it is | Available on |
|---|---|---|
user | The authenticated user, or undefined if the request is anonymous | Always |
req | The request context (headers, query parameters) | Always |
doc | The existing document | Reads and writes of a specific document |
data | The incoming payload | Create and update |
id | The target document's ID | Operations on a specific document |
The user object is the caller's own record, loaded fresh from the database on each request, so it includes whatever fields your auth collection defines. A few properties are always present:
| Property | Type | Meaning |
|---|---|---|
user.sub / user.id | string | The user's document ID |
user.email | string | The user's email address |
user.collection | string | The auth collection the user signed in against |
user.roles | string[] | Role strings, when your auth collection has a roles field |
The scaffolded auth collection ships with a roles field offering admin, editor, and viewer, which is why 'admin' in user.roles is the idiom you will see most often. For the full picture of what lands on user, see Token data.
Guarding against anonymous requests is a matter of checking whether user exists:
access: {
read: "user != null", // any signed-in user
}Where rules live
Access rules attach at three levels, each with its own operations. All three are enforced on the server, on every request:
| Level | Operations | What it protects |
|---|---|---|
| Collections | read, create, update, delete, readAudit | Whole documents in a collection, plus the audit log |
| Globals | read, update | A single global document |
| Fields | read, create, update | Individual fields — stripped from responses or dropped on writes |
Named policies do not attach to content on their own. You define them once under accessPolicies, then reference them from collection, global, or field rules.
Field rules also drive the admin panel, hiding or locking fields in the edit form, but the enforcement is real: a field a reader cannot see is removed from the API response, and a field a writer cannot set is dropped before the document is saved.
Default access
If you do not set a rule for an operation, that operation is open. A collection with no access block can be read, created, updated, and deleted by anyone who can reach the API.
Access is open by default, not locked down. Set explicit rules on anything that holds data you would not publish publicly, and treat a missing rule as "public" while you review your config. To turn an operation off entirely, set it to false rather than omitting it.
A good habit is to start each collection closed and open up only what an operation needs:
access: {
read: "user != null",
create: "'admin' in user.roles",
update: "'admin' in user.roles",
delete: "'admin' in user.roles",
}Where to go next
- Collection access control — the four operations, row-level filtering, and common recipes.
- Global access control — read and update rules for single-document config.
- Field access control — hiding and locking individual fields.
- Hooks — for logic that needs to transform data or raise custom errors, not just allow or deny.
Declarative Expressions & Helpers
Learn how to write Cloud-safe Jexl expressions for field hooks, reactive form logic, and access control rules using Dyrected's built-in helper utility suite.
Dyrected Cloud
Use access control safely in Dyrected Cloud with Jexl rules, serializable named policies, and Cloud's built-in policy registry.