Dyrected
Model ContentContent RulesAccess Control

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 false result denies it — Dyrected responds with 403 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:

ShapeReach for it whenRuns on
BooleanThe answer never changes — always open or always closed.Cloud backend
Jexl stringA serializable check like a role test or field comparison.Cloud backend
Named policyYou 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 toWriteExample
Compare values== != > >= < <=doc.status == 'published'
Combine conditions&& || !user != null && !doc.locked
Check membershipin'admin' in user.roles
Read a property. or ['key']user.roles, doc['status']
Pick between valuescond ? a : buser ? 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:

VariableWhat it isAvailable on
userThe authenticated user, or undefined if the request is anonymousAlways
reqThe request context (headers, query parameters)Always
docThe existing documentReads and writes of a specific document
dataThe incoming payloadCreate and update
idThe target document's IDOperations 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:

PropertyTypeMeaning
user.sub / user.idstringThe user's document ID
user.emailstringThe user's email address
user.collectionstringThe auth collection the user signed in against
user.rolesstring[]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:

LevelOperationsWhat it protects
Collectionsread, create, update, delete, readAuditWhole documents in a collection, plus the audit log
Globalsread, updateA single global document
Fieldsread, create, updateIndividual 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

On this page

Dyrected| Cloud

Get your backend ready in minutes

Use a managed database, storage, APIs, and admin dashboard without setting up the infrastructure yourself.

Set Up My Backend