Dyrected
Examples & RecipesApplication Patterns

Access Control

Application patterns for row-level access, role-based permissions, and deciding who can operate on which content.

Use these patterns when your main concern is who should be allowed to read, create, update, or delete content. They help you move from broad permissions toward rules that match how real teams and users work.

In self-hosted Dyrected, these patterns can use the full application runtime: collection auth, function access rules, function hooks, local policies, and tenant logic that belongs inside your server boundary.

Limit documents to their owner

Problem: signed-in users should only see or manage the records they own.

This pattern returns a where constraint from collection access control and usually pairs it with a server hook that stamps ownership during creation. It is a good fit for customer dashboards, project spaces, and user-owned records.

Example implementation

import { defineCollection, defineRelationshipField, defineTextField } from "@dyrected/core";

export const Projects = defineCollection({
  slug: "projects",
  access: {
    read: ({ user }) => (user ? { owner: { equals: user.sub } } : false),
    create: ({ user }) => Boolean(user),
    update: ({ user }) => (user ? { owner: { equals: user.sub } } : false),
    delete: ({ user }) => (user ? { owner: { equals: user.sub } } : false),
  },
  hooks: {
    beforeChange: [
      ({ data, operation, user }) => {
        if (operation !== "create") return data;
        if (!user) throw new Error("Authentication is required to create a project.");
        return { ...data, owner: user.sub };
      },
    ],
  },
  fields: [
    defineTextField({ name: "name", label: "Project name", required: true }),
    defineRelationshipField({
      name: "owner",
      label: "Owner",
      relationTo: "users",
      required: true,
      admin: { readOnly: true },
    }),
  ],
});

Read the full docs:

Restrict content operations by user role

Problem: different team roles should have different permissions, such as public reads, editor writes, or admin-only deletion.

This pattern uses collection access control to check roles on each operation. It is the right starting point when content needs a clearer separation between editors, reviewers, administrators, or public readers.

Example implementation

import { defineCollection, defineTextField } from "@dyrected/core";

export const Articles = defineCollection({
  slug: "articles",
  access: {
    read: () => true,
    create: ({ user }) => user?.roles?.some((role) => role === "editor" || role === "admin") ?? false,
    update: ({ user }) => user?.roles?.some((role) => role === "editor" || role === "admin") ?? false,
    delete: ({ user }) => user?.roles?.includes("admin") ?? false,
  },
  fields: [defineTextField({ name: "title", label: "Title", required: true })],
});

Read the full docs:

Scope content to the current workspace

Problem: users should only see or manage records that belong to their current workspace or organization.

This pattern stamps a tenant identifier onto new records and returns tenant-scoped access constraints for reads and writes. It is the starting point for multi-workspace SaaS apps where isolation matters as much as authentication.

Example implementation

import { defineCollection, defineTextField } from "@dyrected/core";

const getWorkspaceId = (user?: unknown) => {
  if (!user || typeof user !== "object") return undefined;
  const workspaceId = (user as Record<string, unknown>).workspaceId;
  return typeof workspaceId === "string" && workspaceId.length > 0
    ? workspaceId
    : undefined;
};

export const Projects = defineCollection({
  slug: "projects",
  access: {
    read: ({ user }) =>
      getWorkspaceId(user)
        ? { workspaceId: { equals: getWorkspaceId(user) } }
        : false,
    create: ({ user }) => Boolean(getWorkspaceId(user)),
    update: ({ user }) =>
      getWorkspaceId(user)
        ? { workspaceId: { equals: getWorkspaceId(user) } }
        : false,
    delete: ({ user }) =>
      user?.roles?.includes("admin")
        ? true
        : getWorkspaceId(user)
          ? { workspaceId: { equals: getWorkspaceId(user) } }
          : false,
  },
  hooks: {
    beforeChange: [
      ({ data, operation, user }) => {
        if (operation !== "create") return data;
        const workspaceId = getWorkspaceId(user);
        if (!workspaceId) {
          throw new Error("A workspace is required to create a project.");
        }
        return { ...data, workspaceId };
      },
    ],
  },
  fields: [
    defineTextField({ name: "name", label: "Project name", required: true }),
    defineTextField({
      name: "workspaceId",
      label: "Workspace ID",
      required: true,
      admin: { readOnly: true },
    }),
  ],
});

Read the full docs:

Let owners edit records while admins manage everything

Problem: ordinary users should stay scoped to their own records, but administrators still need a way to intervene.

This pattern combines ownership rules with an admin override. It works well for tickets, support requests, user-owned projects, and other records where support or operations staff may need broader access than the original owner.

Example implementation

import { defineCollection, defineRelationshipField, defineTextField } from "@dyrected/core";

const ownerConstraint = (user?: { sub?: string; roles?: string[] }) => {
  if (user?.roles?.includes("admin")) return true;
  return user?.sub ? { owner: { equals: user.sub } } : false;
};

export const Tickets = defineCollection({
  slug: "tickets",
  access: {
    read: ({ user }) => ownerConstraint(user),
    create: ({ user }) => Boolean(user?.sub),
    update: ({ user }) => ownerConstraint(user),
    delete: ({ user }) => ownerConstraint(user),
  },
  hooks: {
    beforeChange: [
      ({ data, operation, user }) => {
        if (operation !== "create") return data;
        if (!user?.sub) throw new Error("Authentication is required.");
        return { ...data, owner: user.sub };
      },
    ],
  },
  fields: [
    defineTextField({ name: "subject", label: "Subject", required: true }),
    defineRelationshipField({
      name: "owner",
      label: "Owner",
      relationTo: "users",
      required: true,
      admin: { readOnly: true },
    }),
  ],
});

Read the full docs:

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