Dyrected
Model ContentConfiguration

Collections

Use collections for repeatable content such as posts, products, users, and submissions.

Collections are the repeatable side of a Dyrected content model. Use them when you expect more than one document with the same shape, such as blog posts, products, authors, events, form submissions, or users.

Each collection lives in your dyrected.config.ts and automatically shapes the admin UI, API routes, and SDK methods for that content type.

This page is the main home for how collections work in Dyrected. It should explain the collection model in prose and also hold the generated @dyrected/knowledge reference material for the collection contract.

What a collection is

A collection stores many documents under one slug. Each document gets its own id, and each document follows the same field definition.

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

export const Posts = defineCollection({
  slug: "posts",
  labels: { singular: "Post", plural: "Posts" },
  admin: {
    useAsTitle: "title",
    defaultColumns: ["title", "status", "updatedAt"],
    urlPattern: "/blog/{slug}",
  },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", unique: true }),
  ],
});

In practice, the slug becomes a stable identifier for API paths, admin routes, and SDK calls.

What you get automatically

When you define a collection, Dyrected generates the main content surfaces around it:

  • list, read, create, update, and delete collection endpoints
  • a generated admin list view, detail view, and edit form
  • typed SDK access through client.collection('slug')
  • hooks and access points that run on the server

Collections can also opt into behaviors such as authentication, uploads, workflows, and audit logging.

Operational views

A collection can have more than one admin workspace. Add a views array with defineView — Dyrected turns each entry into its own table, board, calendar, gallery, or spreadsheet with a dedicated sidebar item, filter, columns, actions, and metrics.

import {
  defineCollection,
  defineTextField,
  defineBooleanField,
  defineNumberField,
  defineSelectField,
  defineDateTimeField,
  defineView,
  defineAction,
} from "@dyrected/core";

const checkInAction = defineAction({
  name: "checkIn",
  label: "Check In",
  type: "row",
  mutation: { checkedIn: true, checkedInAt: "now()" },
});

export const GuestResponses = defineCollection({
  slug: "guest-responses",
  fields: [
    defineTextField({ name: "name", label: "Full Name", required: true }),
    defineBooleanField({ name: "attending", label: "Attending" }),
    defineNumberField({ name: "guestCount", label: "Plus-Ones" }),
    defineSelectField({ name: "asoebiStatus", label: "Asoebi Status", options: ["requested", "paid", "collected"] }),
    defineDateTimeField({ name: "appointmentDate", label: "Tasting Date" }),
  ],
  views: [
    defineView({
      slug: "attending-guests",
      label: "Attending Guests",
      layout: "table",
      filter: { attending: { equals: true } },
      columns: ["name", "guestCount"],
      actions: [checkInAction],
    }),
    defineView({
      slug: "asoebi-pipeline",
      label: "Asoebi Fulfillment",
      layout: "kanban",
      groupBy: "asoebiStatus",
      columns: ["name", "asoebiStatus"],
    }),
    defineView({
      slug: "tasting-schedule",
      label: "Tasting Schedule",
      layout: "calendar",
      dateField: "appointmentDate",
      columns: ["name", "guestCount"],
    }),
  ],
});

If you omit views, Dyrected synthesizes a list table view from admin.defaultColumns or the first five display fields — so /collections/:slug always has a route. Keep slug and view slugs stable; they are part of the URL and preference keys.

For layouts, actions, metrics, and UX taste, read Operational Views.

When to choose a collection

Choose a collection when the answer to "how many of these exist?" is "more than one" or "probably more than one later."

Common examples:

  • posts
  • products
  • pages
  • team members
  • FAQs
  • contact submissions
  • upload libraries

If the content should exist once for the whole site, it likely belongs in a global instead.

The shape of a collection

Most collections start with the same few decisions. If you can answer these clearly, the rest of the config usually falls into place.

Start with slug

slug is the collection's stable machine name. Dyrected uses it in API routes, admin URLs, SDK calls, and the underlying database table or collection name.

That means slug is part of the long-term data contract, not just display text. Choose a name you can keep, such as posts, products, people, or contact-submissions.

Define the document shape in fields

fields is where the actual content model lives. This array defines what each document contains, what editors can fill in, how values are validated, and what the API returns.

In practice, most collection design work happens here. This is where you model titles, slugs, dates, relationships, nested objects, repeated groups, and anything else that belongs to each document.

For the full field system, read Fields.

Shape the record summary with detail

Collections get a Detail View where editors can review a record before opening the edit form. Dyrected can generate that summary from your fields, or you can define detail to arrange sections, field display formats, computed values, and repeated data yourself.

Use a custom Detail View when the record is easier to understand as a summary than as a full form. For the full guide, read Detail Views.

Use blocks for page sections

If one document needs a flexible sequence of reusable page sections, model that with a blocks field inside fields.

Blocks are not a separate top-level collection option. They are part of the field model, and they are usually the right tool for landing pages, marketing pages, structured articles, and other page-like content where editors need to mix approved section types in different orders.

For more on page sections, read Blocks.

Turn on auth only when documents are users

Use auth when each document in the collection should behave like an account that can sign in, hold credentials, and participate in login flows.

This is usually right for collections like users, admins, members, or customers. It is usually not right for ordinary content collections such as posts, products, or pages.

Turn on upload when each document is a file

Use upload when each collection entry should represent a stored file, such as an image, PDF, video, or downloadable asset.

This is how you model media libraries and file-backed records. If the collection is normal content that only references files, keep the collection itself as a regular collection and use upload-related fields inside fields instead.

Use a global when there should only be one

Collections are for repeatable content. If the content should exist once for the whole site or application, use a global instead.

Good collection examples are posts, authors, products, and events. Good global examples are site-settings, navigation, footer, and seo-defaults.

CollectionConfig

Use this contract when you want the exact shape of a collection config.

Most collection work comes down to a small set of top-level options: giving the collection a stable slug, defining its fields, deciding how it should appear in the Admin UI, and choosing whether it also handles access, hooks, auth, uploads, workflows, or other optional behavior.

Pass your document's TypeScript type as the generic parameter TDoc to get fully typed hooks and access functions.

export interface CollectionConfig<TDoc extends object = Record<string, unknown>> {
  /**
   * Unique identifier for this collection.
   *
   * Dyrected uses the slug for API routes, SDK calls, Admin URLs, and as the
   * underlying database table or collection name. Treat it as part of the
   * long-term data contract rather than a cosmetic label.
   *
   * Use kebab-case, for example `'blog-posts'`, `'team-members'`, or
   * `'contact-submissions'`.
   */
  slug: string;

  /**
   * Restricts this collection to one specific site in a multi-tenant setup.
   *
   * Use this when the collection should belong to a single site rather than
   * the whole installation. When set, only requests bearing a matching
   * `X-Site-Id` header can access it.
   */
  siteId?: string;

  /**
   * If `true`, this collection is shared across all sites in a multi-tenant
   * setup and accessible regardless of the `X-Site-Id` header.
   *
   * Use this for content that should stay common across sites, such as shared
   * taxonomies, reusable assets, or centrally managed reference data.
   */
  shared?: boolean;

  /**
   * Human-readable names for documents in this collection, shown in the Admin UI.
   *
   * Use this when the slug is technical or when you want the dashboard to read
   * more naturally. For example, `slug: 'people'` might use
   * `labels: { singular: 'Person', plural: 'People' }`.
   *
   * @see {@link https://dyrected.com/docs/model-content/configuration/collections#labels Collections labels}
   */
  labels?: {
    singular: string;
    plural: string;
  };

  /**
   * If `true` or an auth config object, this collection is an auth collection. It gains
   * `POST /api/collections/:slug/login` and `POST /api/collections/:slug/logout`
   * endpoints, and documents are expected to have a `password` field.
   *
   * Turn this on when each document should behave like an account that can log
   * in, hold credentials, and participate in user flows. Typical examples are
   * `users`, `admins`, `members`, or `customers`.
   *
   * Pass an object when you want to tune built-in account lockout behavior for
   * repeated failed logins.
   *
   * @see {@link https://dyrected.com/docs/editor-experience/editor-accounts Authentication overview}
   */
  auth?: boolean | AuthConfig;

  /**
   * If `true` or a config object, this collection supports file uploads.
   * Documents gain file-related fields (`url`, `filename`, `mimeType`, etc.)
   * and the create endpoint accepts `multipart/form-data`.
   *
   * Turn this on when each document in the collection should represent a
   * stored file, such as an image, PDF, video, or downloadable asset.
   *
   * @see {@link https://docs.dyrected.com/docs/self-hosted/model-content/media/overview Upload overview}
   */
  upload?: boolean | UploadConfig;

  /**
   * Field definitions that make up the document schema for this collection.
   *
   * This is the main schema contract for every document in the collection. It
   * decides what editors can fill in, how data is validated, how records are
   * stored, and what the API and SDK return.
   *
   * In practice, fields are where you model the actual content structure of the
   * collection: simple values such as text and dates, relationships to other
   * collections, nested objects and arrays, and flexible `blocks` fields for
   * reusable page sections or long-form layouts.
   *
   * @see {@link https://dyrected.com/docs/model-content/fields/overview Fields overview}
   * @see {@link https://dyrected.com/docs/model-content/fields/blocks Blocks and page sections}
   */
  fields: Field[];

  /**
   * If `true`, Dyrected automatically adds the built-in system fields
   * `createdAt`, `updatedAt`, `createdBy`, and `updatedBy` to every document.
   * Defaults to `true`.
   */
  timestamps?: boolean;

  /**
   * Initial documents to seed into this collection the first time it is
   * fetched and found to be empty.
   *
   * Use this for starter records, demo content, or sensible defaults that
   * should appear automatically before editors create anything themselves.
   */
  initialData?: Partial<TDoc>[];

  /**
   * If `true`, every create, update, and delete operation on this collection
   * is logged to the `__audit` collection with before/after snapshots and the
   * acting user's identity.
   *
   * Turn this on when you need accountability around changes, such as knowing
   * who changed what, inspecting before-and-after state, or supporting
   * compliance and operational review.
   */
  audit?: boolean;

  /**
   * Optional state-machine workflow for this collection. Workflow-enabled
   * entries keep an editable working revision and an independent public
   * snapshot, so editing published content never changes the live response.
   *
   * Use this when content moves through stages such as draft, review, and
   * published, or when teams need an approval process before changes go live.
   */
  workflow?: WorkflowConfig<TDoc>;

  /**
   * If `true`, enables zero-config draft and publish functionality.
   * Documents start as drafts, editors can save working drafts without affecting
   * live content, and any authorized editor can publish or unpublish entries.
   */
  drafts?: boolean;

  /**
   * Collection-level access control.
   *
   * Each key is an operation; the value can be a function, a Jexl string, a
   * boolean, or a named policy reference. Returning `true` allows access and
   * `false` denies it. Returning a `where`-style object grants access only to
   * matching documents.
   *
   * @example
   * access: {
   *   read: () => true,
   *   create: ({ user }) => !!user,
   *   update: ({ user }) => user?.roles?.includes('editor') ?? false,
   *   delete: ({ user }) => user?.roles?.includes('admin') ?? false,
   * }
   *
   * @see {@link https://docs.dyrected.com/docs/self-hosted/model-content/content-rules/access-control/overview Access control overview}
   */
  access?: {
    read?: AccessRule<TDoc>;
    create?: AccessRule<TDoc>;
    update?: AccessRule<TDoc>;
    delete?: AccessRule<TDoc>;
    /**
     * Controls who can read this collection's audit log (`GET /:slug/__audit`),
     * for collections with `audit` enabled. Falls back to the `read` rule when
     * omitted, so the audit trail is visible to whoever can read the documents.
     * Set it explicitly to gate the audit log separately — for example, admins
     * only, even on a collection anyone can read.
     */
    readAudit?: AccessRule<TDoc>;
  };

  /**
   * Collection-level lifecycle hooks.
   *
   * Hooks run in the order they appear in the array. The return value of each
   * hook is passed as the input to the next. Throwing inside any hook aborts
   * the operation and returns a `500` error.
   *
   * See the Hooks reference for the full lifecycle diagram.
   *
   * @see {@link https://docs.dyrected.com/docs/self-hosted/model-content/content-rules/hooks Hooks overview}
   * @see {@link https://docs.dyrected.com/docs/self-hosted/deployment-and-operations/server-runtime/hooks/collections Collection hooks}
   */
  hooks?: {
    /**
     * Runs before the database is queried. Return a modified `where` object
     * to override the query filter.
     */
    beforeRead?: CollectionBeforeReadHookEntry[];

    /**
     * Runs after documents are fetched. Return a modified doc to change what
     * the client receives. Runs on every document in a list response.
     */
    afterRead?: CollectionAfterReadHookEntry<TDoc>[];

    /**
     * Runs before create or update. Return modified data to change what is
     * written to the database. Throw to abort the write entirely.
     */
    beforeChange?: CollectionBeforeChangeHookEntry<TDoc>[];

    /**
     * Runs after create or update is committed. For side-effects only:
     * webhooks, cache busting, and notifications. Return value is ignored.
     *
     * Errors are isolated: caught, logged, and discarded so a failing
     * side-effect never turns a successful write into an HTTP 500.
     * See `CollectionAfterChangeHook` for await-vs-fire-and-forget guidance.
     */
    afterChange?: CollectionAfterChangeHook<TDoc>[];

    /** Runs before a document is deleted. Throw to cancel the deletion. */
    beforeDelete?: CollectionBeforeDeleteHook<TDoc>[];

    /**
     * Runs after a document has been deleted. For cleanup side-effects only.
     *
     * Errors are isolated: caught, logged, and discarded. The deletion is
     * already committed and will not be undone.
     */
    afterDelete?: CollectionAfterDeleteHook<TDoc>[];
  };

  /**
   * Admin UI configuration for this collection.
   *
   * @see {@link https://dyrected.com/docs/model-content/configuration/collections#admin-options Admin options}
   */
  admin?: {
    /**
     * Lucide icon displayed beside this collection in the Admin sidebar.
     * Uses Lucide component names, e.g. `'Newspaper'` or `'ShoppingBag'`.
     */
    icon?: AdminIconName;

    /** Custom component slots for this collection's list view. */
    components?: CollectionListComponentSlots;

    /**
     * The field name used as the document's display title in the Admin list
     * view and breadcrumbs. Defaults to `'title'` if the field exists.
     */
    useAsTitle?: string;

    /**
     * Field names to show as columns in the Admin list view.
     * Defaults to a sensible set of the first few non-structural fields.
     */
    defaultColumns?: string[];

    /** Short helper copy rendered under the collection title in the Admin list view. */
    description?: string;

    /**
     * Field names included in backend free-text search for this collection.
     * When omitted, Dyrected infers a conservative default from common text-like fields.
     */
    searchableFields?: string[];

    /**
     * Groups this collection under a named section in the Admin sidebar.
     * Collections with the same `group` are visually grouped together.
     */
    group?: string;

    /**
     * Slug of the view to render by default when navigating to `/collections/:slug`.
     * If not specified, defaults to the view with `default: true` or the all-records table view.
     */
    defaultView?: string;

    /** If `true`, this collection is not shown in the Admin UI sidebar. */
    hidden?: boolean;

    /** If `false`, disables the filter UI entirely for this collection. Defaults to `true`. */
    filterable?: boolean;

    /**
     * Enables draft autosave in the Admin editor for workflow-enabled
     * collections. Defaults to `true` when the collection uses `workflow` or
     * `drafts: true`.
     */
    autosave?: boolean;

    /**
     * Debounce duration in milliseconds for Admin draft autosave.
     * Defaults to `1500`.
     */
    autosaveDelayMs?: number;

    /**
     * URL to open in the Live Preview pane when editing a document.
     *
     * Pass a Jexl string to keep the config serializable, for example
     * `'slug == "home" ? "/" : "/" + slug'`. This is usually the best
     * default, especially when the schema needs to stay portable across
     * environments such as Dyrected Cloud.
     *
     * Pass a function when you need custom runtime logic in a self-hosted
     * project.
     *
     * @example
     * previewUrl: 'slug == "home" ? "/" : "/" + slug'
     *
     * @example
     * previewUrl: (doc) => `/blog/${doc.slug}`
     */
    previewUrl?: string | ((doc: TDoc, opts: { locale?: string }) => string | null);

    /**
     * How the Live Preview pane communicates with the frontend.
     * - `postMessage` sends a `postMessage` with the current doc data.
     * - `token` passes a short-lived preview token as a query parameter.
     */
    previewMode?: "postMessage" | "token";

    /**
     * Frontend URL pattern for this collection, used by `url` fields to
     * resolve internal links. Use `{fieldName}` placeholders.
     *
     * This is a plain route pattern string, not a Jexl expression.
     *
     * @example
     * urlPattern: '/blog/{slug}' // /blog/my-post
     * urlPattern: '/{slug}' // /about
     */
    urlPattern?: string;
  };

  /**
   * Custom detail view layout configuration for the Admin UI.
   */
  detail?: DetailSchema<TDoc> | false;

  /**
   * Slug of the operational view to render by default when navigating to `/collections/:slug`.
   *
   * When set:
   * 1. Navigating to `/collections/:slug` automatically redirects to `/collections/:slug/views/:defaultView`.
   * 2. The main collection link in the sidebar directly opens this default view.
   * 3. The sidebar submenu cleanly lists only your defined views without showing a generic `All [Collection]` item.
   *
   * @example
   * ```ts
   * export const GuestResponses = defineCollection({
   *   slug: "guest-responses",
   *   defaultView: "attending-guests",
   *   views: [attendingGuests, seatingMatrix],
   *   fields: [...],
   * });
   * ```
   */
  defaultView?: string;

  /**
   * Tailored operational workspaces for this collection (`table`, `spreadsheet`, `kanban`, `calendar`, `cards`, `gantt`).
   * Each view provides curated columns, filters, metrics, and workflow buttons for a specific job.
   *
   * @example
   * ```ts
   * views: [
   *   defineView({
   *     slug: "attending-guests",
   *     label: "Attending Guests",
   *     icon: "UserCheck",
   *     layout: "table",
   *     groupBy: "tableNumber",
   *     filter: { attending: { equals: true } },
   *     columns: ["name", "email", "guestCount", "checkedIn"],
   *   }),
   * ]
   * ```
   *
   * @see {@link https://dyrected.com/docs/model-content/operational-views/overview Operational views overview}
   */
  views?: ViewConfig[];
}
OptionDescription
slug (required)Unique identifier for this collection. Dyrected uses the slug for API routes, SDK calls, Admin URLs, and as the underlying database table or collection name. Treat it as part of the long-term data contract rather than a cosmetic label. Use kebab-case, for example `'blog-posts'`, `'team-members'`, or `'contact-submissions'`.
siteId (optional)Restricts this collection to one specific site in a multi-tenant setup. Use this when the collection should belong to a single site rather than the whole installation. When set, only requests bearing a matching `X-Site-Id` header can access it.
shared (optional)If `true`, this collection is shared across all sites in a multi-tenant setup and accessible regardless of the `X-Site-Id` header. Use this for content that should stay common across sites, such as shared taxonomies, reusable assets, or centrally managed reference data.
labels (optional)Human-readable names for documents in this collection, shown in the Admin UI. Use this when the slug is technical or when you want the dashboard to read more naturally. For example, `slug: 'people'` might use `labels: { singular: 'Person', plural: 'People' }`. See: Collections labels
auth (optional)If `true` or an auth config object, this collection is an auth collection. It gains `POST /api/collections/:slug/login` and `POST /api/collections/:slug/logout` endpoints, and documents are expected to have a `password` field. Turn this on when each document should behave like an account that can log in, hold credentials, and participate in user flows. Typical examples are `users`, `admins`, `members`, or `customers`. Pass an object when you want to tune built-in account lockout behavior for repeated failed logins. See: Authentication overview
upload (optional)If `true` or a config object, this collection supports file uploads. Documents gain file-related fields (`url`, `filename`, `mimeType`, etc.) and the create endpoint accepts `multipart/form-data`. Turn this on when each document in the collection should represent a stored file, such as an image, PDF, video, or downloadable asset. See: Upload overview
fields (required)Field definitions that make up the document schema for this collection. This is the main schema contract for every document in the collection. It decides what editors can fill in, how data is validated, how records are stored, and what the API and SDK return. In practice, fields are where you model the actual content structure of the collection: simple values such as text and dates, relationships to other collections, nested objects and arrays, and flexible `blocks` fields for reusable page sections or long-form layouts. See: Fields overview, Blocks and page sections
timestamps (optional)If `true`, Dyrected automatically adds the built-in system fields `createdAt`, `updatedAt`, `createdBy`, and `updatedBy` to every document. Defaults to `true`.
initialData (optional)Initial documents to seed into this collection the first time it is fetched and found to be empty. Use this for starter records, demo content, or sensible defaults that should appear automatically before editors create anything themselves.
audit (optional)If `true`, every create, update, and delete operation on this collection is logged to the `__audit` collection with before/after snapshots and the acting user's identity. Turn this on when you need accountability around changes, such as knowing who changed what, inspecting before-and-after state, or supporting compliance and operational review.
workflow (optional)Optional state-machine workflow for this collection. Workflow-enabled entries keep an editable working revision and an independent public snapshot, so editing published content never changes the live response. Use this when content moves through stages such as draft, review, and published, or when teams need an approval process before changes go live.
drafts (optional)If `true`, enables zero-config draft and publish functionality. Documents start as drafts, editors can save working drafts without affecting live content, and any authorized editor can publish or unpublish entries.
access (optional)Collection-level access control. Each key is an operation; the value can be a function, a Jexl string, a boolean, or a named policy reference. Returning `true` allows access and `false` denies it. Returning a `where`-style object grants access only to matching documents. See: Access control overview
hooks (optional)Collection-level lifecycle hooks. Hooks run in the order they appear in the array. The return value of each hook is passed as the input to the next. Throwing inside any hook aborts the operation and returns a `500` error. See the Hooks reference for the full lifecycle diagram. See: Hooks overview, Collection hooks
admin (optional)Admin UI configuration for this collection. See: Admin options
detail (optional)Custom detail view layout configuration for the Admin UI.
defaultView (optional)Slug of the operational view to render by default when navigating to `/collections/:slug`. When set: 1. Navigating to `/collections/:slug` automatically redirects to `/collections/:slug/views/:defaultView`. 2. The main collection link in the sidebar directly opens this default view. 3. The sidebar submenu cleanly lists only your defined views without showing a generic `All [Collection]` item.
views (optional)Tailored operational workspaces for this collection (`table`, `spreadsheet`, `kanban`, `calendar`, `cards`, `gantt`). Each view provides curated columns, filters, metrics, and workflow buttons for a specific job. See: Operational views overview

admin options

Use admin to control how this collection feels inside the dashboard. These options do not change the stored schema, but they do change how editors find, scan, and work with documents.

OptionWhat it does
useAsTitleChooses which field Dyrected should treat as the human-readable name for each document in list views, breadcrumbs, relationship pickers, and other admin surfaces. Use the field editors recognize fastest, usually something like title, name, or email. This is type-checked at config authoring time against your real top-level field names.
defaultColumnsSets the columns shown in the collection list view before an editor customizes anything. Pick columns that help someone scan and make a decision quickly, such as title, status, updatedAt, publishedAt, or author.
searchableFieldsSets which top-level fields backend list search should query for this collection. Use this when editors need search to cover more than the title, or when you want tighter control over query cost and match quality. This is type-checked at config authoring time against your real top-level field names.
descriptionAdds a short line of helper text under the collection title on the admin list page. Use it for context such as what the collection powers, what belongs there, or any quick editorial guidance.
groupPlaces the collection under a named section in the admin sidebar. Use it when several collections belong to one area of work, such as Content, Commerce, or People.
iconAdds a Lucide icon to the collection in the sidebar and related admin navigation. This is mostly a usability and visual organization option, but it helps large dashboards feel easier to scan.
hiddenHides the collection from the admin sidebar while keeping it in the system. This can be useful for internal collections, support data, or collections managed indirectly through other workflows.
filterableControls whether the collection list view shows filter UI. Leave this on for most editorial collections, and turn it off only when filtering adds no value or when you want a simpler workflow.
previewUrlTells Dyrected where a document should open for live preview while someone is editing it. It can be a Jexl string or a function. Prefer a Jexl string as the default because it stays serializable and fits Dyrected Cloud-style schema synchronization more cleanly. Use a function only when you need custom runtime logic in a self-hosted setup.
previewModeControls how the live preview pane communicates with your frontend. Use the default approach that matches your preview setup, and only customize this when you need a specific integration behavior.
urlPatternGives Dyrected a route pattern for documents in this collection, such as /blog/{slug}. This is a placeholder pattern, not a Jexl expression. Use {fieldName} tokens to describe the path shape.

access

Collection access rules run on the server. They decide who can read, create, update, or delete documents.

This is where you enforce business rules such as:

  • everyone can read published posts
  • only signed-in users can create submissions
  • editors can update content
  • only admins can delete records
  • users can only see documents that belong to them

Use collection access when permissions depend on the request, the current user, or the document being queried.

See Access Control Overview for the full model.

Authoring-time validation

Dyrected validates several string references in TypeScript while you are writing config, so editors and developers get immediate feedback instead of a broken admin screen later.

Today that includes:

  • collection admin.useAsTitle, admin.defaultColumns, and admin.searchableFields
  • nested array and object field admin.useAsTitle
  • named access policy references such as { policy: "canReadPosts" }
  • relationship collection slugs in relationTo
  • reusable block slugs in blockReferences
  • upload-enabled collection references for rich text uploads and image fields
  • auth collection references such as adminAuth.collectionSlug
  • workflow state names in transitions and initial state

This is only authoring-time validation. Dyrected still normalizes and runs your schema at runtime, but the TypeScript layer now catches many of the most common "why is this config not working?" mistakes much earlier.

hooks

Hooks let you run server-side logic around reads and writes. Use them to validate input, derive fields, trigger revalidation, send notifications, sync outside systems, or clean up related data.

Collection hooks are especially useful when a rule should always run with the collection itself instead of being left to a frontend form or a separate script.

See Hooks Overview for the lifecycle and Collection Hooks for collection-focused examples.

auth

Set auth: true when this collection should represent application users and support login flows.

An auth collection gains authentication endpoints such as login and logout, and its documents are treated as user records rather than plain content entries. In practice, this is what you use for collections like users, members, customers, or admins.

Turning on auth changes the role of the collection. It is not just "add a password field" but "this collection now participates in authentication."

For the full auth model, token behavior, and login strategies, read Authentication Overview.

upload

Set upload: true or pass an upload config when each document in the collection should represent a stored file.

An upload collection is how you model media libraries and file-backed entries. Dyrected adds file-related properties such as url, filename, and mimeType, and the collection can accept multipart file uploads instead of only JSON documents.

Use this for collections like media, documents, or brand asset libraries.

For storage behavior and adapter-level upload setup, read Upload Overview. For the field-level side of file relationships, read Upload Field.

initialData

Set initialData when a collection should start with a few records instead of empty. Dyrected seeds these documents the first time the collection is fetched and found empty, so a fresh project or a new environment opens with content already in place.

Because a collection holds many documents, initialData is an array, and each entry becomes one starter document:

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

export const Categories = defineCollection({
  slug: "categories",
  labels: { singular: "Category", plural: "Categories" },
  fields: [
    defineTextField({ name: "name", label: "Name", required: true }),
    defineTextField({ name: "slug", label: "Slug", required: true, unique: true }),
  ],
  initialData: [
    { name: "News", slug: "news" },
    { name: "Guides", slug: "guides" },
    { name: "Changelog", slug: "changelog" },
  ],
});

This is handy for reference data most projects need on day one, such as categories, tags, default pages, or demo content you want visible before an editor creates anything.

Seeding only happens while the collection is still empty. Once any document exists, initialData no longer applies, so editors can add, edit, or delete records without Dyrected re-seeding them. Treat it as a starting point, not an ongoing sync.

workflow

Set workflow when a collection needs editorial review or staged publishing. A workflow-enabled collection keeps an editable working revision separate from the public snapshot, so editing a document never changes live content until you transition it to a published state.

This is a larger feature with its own states, transitions, and roles, so it lives on its own page. For the full model, read Workflows Overview.

Workflow draft autosave

Workflow-enabled collections also control how draft saving behaves in the Admin. If a collection uses either workflow or drafts: true, Dyrected treats it as a workflow editing surface:

  • the working draft is separate from the published snapshot
  • publishing and every other workflow transition stay explicit
  • draft autosave is on by default in the Admin

Use the collection's admin config when you need to tune that editor behavior. The simplest example is a collection using drafts: true:

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

export const Posts = defineCollection({
  slug: "posts",
  drafts: true,
  admin: {
    autosave: true,
    autosaveDelayMs: 1500,
  },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
  ],
});
  • admin.autosave defaults to true for collections that use workflow or drafts: true
  • admin.autosave: false keeps explicit manual draft saving for that collection
  • admin.autosaveDelayMs sets the debounce in milliseconds and defaults to 1500

The same admin options work the same way on custom workflow collections too. The difference is only the workflow model itself, not the autosave config surface.

This setting is Admin-only. It changes how the working draft is persisted while someone edits, but it never publishes content, bypasses review, or changes the public snapshot.

audit

Set audit: true when changes to a collection need accountability. Dyrected then logs every create, update, and delete to a hidden audit collection, each entry capturing a before-and-after snapshot and the acting user.

For what gets recorded and how it works, read Audit Overview.

Routable collections

Some collections are not just stored records. They also map to real frontend URLs, such as pages, posts, case studies, projects, or products.

When a collection is routable, the collection config and the frontend route need to agree. In practice, that usually means:

  • a document field such as slug that stores the URL segment
  • admin.previewUrl so editors can open the real frontend page while editing
  • admin.urlPattern so Dyrected knows how that document maps to a frontend path
  • a real frontend route that can fetch and render the document

admin.previewUrl can be either:

  • a Jexl string evaluated against the document, usually the best default
  • a function that returns the URL from the document data

Prefer the Jexl form when you can. It keeps the config serializable, is easier to reason about, and is the safer default when the schema needs to stay portable across environments such as Dyrected Cloud.

admin.urlPattern is different. It is not Jexl. It is a plain route pattern string that uses {fieldName} placeholders, such as /blog/{slug} or /{locale}/{slug}.

For example, this Jexl expression uses a condition to route the home page so any collection entry with the slug == 'home' be previewed at /:

previewUrl: "slug == 'home' ? '/' : '/' + slug";

Here is a typical blog-style example using Jexl:

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

export const Posts = defineCollection({
  slug: "posts",
  admin: {
    useAsTitle: "title",
    previewUrl: "'/blog/' + slug",
    urlPattern: "/blog/{slug}",
  },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", required: true, unique: true }),
  ],
});

If you are self-hosting and need logic that is easier to express in JavaScript, a function also works:

admin: {
  previewUrl: (doc) => (doc.slug === "home" ? "/" : `/${doc.slug}`),
}

In both examples, the collection itself is posts, but each document also has its own slug field. The frontend route pattern comes from the document field, not from the top-level collection slug.

urlPattern placeholders can use any document field names that make sense for the route, not just slug. For example:

  • /blog/{slug}
  • /projects/{projectCode}
  • /people/{username}
  • /{locale}/{slug}

Use slug only for routing and URL generation. For the admin display title, point admin.useAsTitle to a human-readable field such as title or name.

If the frontend cannot actually render the route, the collection is not truly routable yet. Preview links and URL patterns should reflect routes that already exist in the application.

For field-level modeling, read Fields.

Hooks are optional here. You only need them if you want Dyrected to derive routable values automatically, such as generating a document slug from a title before save. For that workflow, read Hooks Overview.

Changing slug names

Change slugs carefully. In Dyrected, there are two different kinds of slug changes, and each has different consequences.

Changing the collection slug

The top-level collection slug is infrastructure. Changing it affects API routes, SDK calls, admin URLs, and the underlying database table or collection name.

That means renaming a collection slug is a schema and integration change, not a cosmetic edit. Any code that reads client.collection("old-slug"), any REST calls, and any tooling that depends on that slug will need to move with it.

If a collection already has live data or production consumers, prefer keeping the collection slug stable unless you are doing a deliberate migration.

Changing a document slug field

Many routable collections also have a document field named slug. That is different from the top-level collection slug.

Changing a document slug field changes the frontend URL for that entry. That can break inbound links, bookmarks, SEO paths, preview expectations, or hard-coded links elsewhere in the app unless you handle redirects or route migration on the frontend.

Treat those changes as URL migrations, not just content edits.

Practical habits

  • Use explicit human-readable labels for named fields.
  • Set admin.useAsTitle to the field editors will recognize fastest.
  • Set admin.searchableFields when backend search should cover more than the title or when you want tighter control over which fields participate.
  • Add admin.description when the collection benefits from one short line of context on the list page.
  • Set admin.icon to an appropriate icon for the collection.
  • Keep routable collections aligned with real frontend routes through previewUrl and urlPattern.
  • Treat field renames and slug changes as schema migrations, not cosmetic edits.
  • See Globals for singleton content.
  • See Overview for the full root config shape.
  • See Fields for the schema system that powers collection documents.

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