Dyrected
Model ContentContent RulesAccess Control

Dyrected Cloud

Use access control safely in Dyrected Cloud with Jexl rules, serializable named policies, and Cloud's built-in policy registry.

Use Dyrected Cloud access control when your content backend is hosted by Dyrected and your rules need to survive sync:schema. Cloud can enforce rules that are serializable: booleans, Jexl strings, serializable named policies, and Cloud's built-in policies.

If you only remember three things from this page, remember these:

  • Jexl strings are the safest default for Cloud.
  • Serializable named policies work when the policy value can sync as schema data.
  • Function rules belong in self-hosted Dyrected, not Cloud schemas.

If you are new to access control itself, start with the Access Control overview. This page is the Cloud-specific guide.

For most Cloud projects, start here:

  1. use Jexl strings for collection, global, and field rules
  2. use a built-in Cloud policy when the policy name matches what you mean
  3. use a site-defined named policy only when its value is a string or boolean

That keeps your access rules portable. They can be stored with the site schema, synced to Dyrected Cloud, and enforced by the hosted content backend.

What works in Cloud

Dyrected Cloud can enforce these access rule shapes:

Rule shapeCloud supportGood default?Notes
true / falseYesSometimesBest for always-open or always-closed rules.
Jexl stringYesYesBest general-purpose Cloud rule.
Named policy referenceYes, for built-in or serializable policiesSometimesGood when a reusable policy name makes the rule clearer.

Here is a Cloud-safe rule that lets signed-in users see everything while anonymous readers only see published content:

access: {
  read: "user != null ? true : { status: { equals: 'published' } }",
}

This works in Cloud because the rule is a Jexl string. Cloud can store it with the synced schema and evaluate it in the hosted backend.

What sync:schema sends to Cloud

When you run npx dyrected sync:schema, Dyrected sends your serializable schema to the Cloud backend. Collection rules, global rules, field rules, and string or boolean policy values can sync.

Cloud sync keeps access-control data that can be represented as schema:

Policy formCloud sync result
accessPolicies: { canRead: true }Kept
accessPolicies: { canRead: "user != null" }Kept
{ policy: 'isAuthenticated' }Kept because it resolves to a Cloud built-in

Function rules and function policies do not become Cloud behavior. If a rule needs local services, direct database checks, side effects, or arbitrary server code, put that behavior in your application backend or use self-hosted Dyrected.

Site-defined policies

Use site-defined policies when you want a reusable name for a rule that still stays serializable.

This policy works in Cloud because canManageOrders is a Jexl string:

export default defineConfig({
  accessPolicies: {
    canManageOrders: "'admin' in user.roles || 'editor' in user.roles",
  },
  collections: [
    defineCollection({
      slug: 'orders',
      access: {
        update: { policy: 'canManageOrders' },
      },
      fields: [
        { name: 'status', label: 'Status', type: 'text' },
      ],
    }),
  ],
  globals: [],
})

After sync, Cloud can resolve update: { policy: 'canManageOrders' } because the policy value was sent as schema data.

Use this rule:

  • string and boolean policies can be user-defined and synced
  • built-in Cloud policies are available without defining them in your site config
  • policies that require executable code are self-hosted-only

Built-in named policies in Dyrected Cloud

Dyrected Cloud currently ships with these built-in named policies in its backend registry:

PolicyUse it whenCommon params
isAuthenticatedThe caller must be signed in.—
hasRoleAccess depends on one or more role strings.role, roles
isSelfThe record should match the current user's own ID.field
isOwnerThe record should match a custom ownership field such as owner, ownerId, or customerId.field
isTenantMemberContent should stay inside the current workspace, site, or tenant boundary.field, tenantId
hasCapabilityAccess depends on one or more capability strings.capability, capabilities
isPublishedPublic reads should only see published entries.field, value
canReadDraftsCertain roles or capabilities may read drafts.roles, capabilities
fieldEqualsYou want a reusable equals filter.field, value
fieldInYou want a reusable in filter.field, values
createdByCurrentUserThe record should match the user who originally created it.field
notSelfThe record should match everyone except the current user.field

These built-ins sit alongside any site-defined string or boolean policies that reached Cloud through sync:schema.

The rest of this page focuses on when to use them, not only what they are called.

Choose the right rule first

When you are writing a Cloud rule, use this quick decision guide:

You want to sayBest fit
"Anyone can read this."read: true
"Only signed-in users can do this."Jexl: "user != null" or policy: { policy: 'isAuthenticated' }
"Only admins or editors can do this."Jexl role check, { policy: 'hasRole', params: { roles: [...] } }, or a synced string policy such as { policy: 'canManageOrders' }
"Users can only see their own rows."{ policy: 'isSelf' }, { policy: 'isOwner' }, or { policy: 'createdByCurrentUser' } depending on the field
"Users can only see rows for this workspace or tenant."{ policy: 'isTenantMember' }
"Anonymous readers only see published content."Jexl ternary or { policy: 'isPublished' } on public reads

If the rule needs to react live in the admin form for field-level access, prefer Jexl. A Jexl field rule can be reflected in the editor UI and enforced by the API.

createdByCurrentUser vs isOwner

This is the most important distinction on the page.

Both policies return the same kind of result: a filter that compares a field on the document to the current user's ID. The difference is not a hidden engine behavior. The difference is the field they target by default and the meaning you are expressing.

createdByCurrentUser

Use createdByCurrentUser when the rule means:

"The current user may act on records they originally created."

By default, this policy checks the built-in createdBy field:

access: {
  read: { policy: 'createdByCurrentUser' },
}

That is the named-policy version of:

access: {
  read: "{ createdBy: { equals: user.sub } }",
}

This is a good fit for:

  • contact submissions a user opened themselves
  • support tickets the user submitted
  • personal notes or drafts where "creator" is the rule you care about

Dyrected adds createdBy automatically through its built-in system fields when timestamps are enabled, and timestamps default to true. So this policy is especially convenient when you want to lean on that built-in field instead of creating your own.

isOwner

Use isOwner when the rule means:

"The current user may act on records owned by them according to this schema."

By default, isOwner checks a field named owner:

access: {
  read: { policy: 'isOwner' },
}

That is the named-policy version of:

access: {
  read: "{ owner: { equals: user.sub } }",
}

In real projects, you will often pass a custom field because many schemas use names like ownerId, customerId, or accountId:

access: {
  read: { policy: 'isOwner', params: { field: 'ownerId' } },
}

This is a good fit for:

  • a project owned by one account
  • an order owned by a customer
  • a workspace record assigned to an owner
  • any document where ownership is a first-class business concept

The practical difference

Use this rule of thumb:

If your question is...Use
"Who created this record?"createdByCurrentUser
"Who owns this record right now?"isOwner

That distinction matters because creator and owner are often not the same concept in a real app.

If you point both policies at the same field, they behave the same way. For example, createdByCurrentUser and isOwner with params: { field: 'createdBy' } both return the same kind of equality filter. The dedicated policy name exists to make the rule's intent obvious to the next person reading the schema.

For example:

  • an admin could create an order on behalf of a customer
  • a team member could create a project that belongs to a workspace owner
  • a record could be reassigned later, while createdBy still points to the original creator

Cloud does not infer those semantics for you. It only compares the current user ID to the field you chose. Your job is to pick the field that matches your business meaning.

Side-by-side example

Here is the same collection using both policies so the difference is obvious:

export const Orders = defineCollection({
  slug: 'orders',
  access: {
    // "Users can read orders they created"
    read: { policy: 'createdByCurrentUser' },

    // "Users can update orders they own"
    update: { policy: 'isOwner', params: { field: 'customerId' } },
  },
  fields: [
    { name: 'customerId', label: 'Customer', type: 'text', required: true },
    { name: 'status', label: 'Status', type: 'text' },
  ],
})

In that example:

  • read follows the built-in createdBy
  • update follows the custom business field customerId

That is a valid setup when "who created it" and "who should control it" are different rules.

When to use isSelf instead

Use isSelf when the record itself is the user's own record.

For example, a profile collection usually wants:

access: {
  read: { policy: 'isSelf' },
  update: { policy: 'isSelf' },
}

That is clearer than isOwner when the target document is the user's own account, profile, or member record.

Use this rule of thumb:

  • isSelf: "this row is me"
  • isOwner: "this row belongs to me"
  • createdByCurrentUser: "I created this row"

Practical Cloud examples

Public published content, private drafts

Use a Jexl ternary when public readers should only see published entries but signed-in staff should see everything:

access: {
  read: "user != null ? true : { status: { equals: 'published' } }",
}

This is often the cleanest Cloud-safe pattern because one rule covers both audiences.

Role-based writing access

Use hasRole when your collections follow a simple admin/editor model:

access: {
  create: { policy: 'hasRole', params: { roles: ['admin', 'editor'] } },
  update: { policy: 'hasRole', params: { roles: ['admin', 'editor'] } },
  delete: { policy: 'hasRole', params: { role: 'admin' } },
}

If the same logic must also drive field-level admin behavior, the equivalent Jexl rule is often easier:

access: {
  update: "'admin' in user.roles || 'editor' in user.roles",
}

If you prefer a project-local policy name, use a synced string policy:

export default defineConfig({
  accessPolicies: {
    canManageOrders: "'admin' in user.roles || 'editor' in user.roles",
  },
  collections: [
    defineCollection({
      slug: 'orders',
      access: {
        create: { policy: 'canManageOrders' },
        update: { policy: 'canManageOrders' },
      },
      fields: [
        { name: 'status', label: 'Status', type: 'text' },
      ],
    }),
  ],
  globals: [],
})

Tenant-scoped data

Use isTenantMember when documents should stay inside the current site or workspace boundary:

access: {
  read: { policy: 'isTenantMember', params: { field: 'workspaceId' } },
  update: { policy: 'isTenantMember', params: { field: 'workspaceId' } },
}

This is a good fit for multi-tenant project, invoice, or asset collections.

Reusable equals and in filters

Use fieldEquals and fieldIn when you want small reusable filters without inventing new custom policy names:

access: {
  read: { policy: 'fieldEquals', params: { field: 'status', value: 'published' } },
}
access: {
  read: { policy: 'fieldIn', params: { field: 'status', values: ['review', 'published'] } },
}

These are especially useful when you want a named-policy shape but the logic is still simple.

Jexl vs built-in policy in Cloud

In Cloud, use this rule of thumb:

Reach forWhen
Jexl stringYou want the most portable Cloud-safe rule, especially for field access and admin reactivity.
Named policyYou want a reusable Cloud-backed rule and the policy value is serializable or built in.
Self-hosted DyrectedThe rule needs application-server code, local services, or side effects.

If you are unsure, start with Jexl. Move to a named policy when the rule becomes repetitive or the policy name makes the intent clearer.

Troubleshooting

"It worked locally, but Cloud denied it"

Check these first:

  • Did the rule you tested locally depend on application-server code?
  • Is the policy value a Jexl string, a boolean, or a Cloud built-in?
  • Should this rule be an inline Jexl string instead?

"I am not sure whether to use createdByCurrentUser or isOwner"

Ask this question:

Do I care about the document's original creator, or the document's business owner?

Then map the answer like this:

  • original creator: createdByCurrentUser
  • business owner field such as ownerId or customerId: isOwner
  • the document is the user's own record: isSelf

"The admin form is not hiding or locking fields the way I expected"

For field access, prefer inline Jexl when the admin form should react live in the same way as the API.

"I changed access rules, but Cloud still behaves the old way"

Run:

npx dyrected sync:schema

If the updated schema did not reach Cloud, the old rules are still active there.

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