Dyrected
Examples & RecipesApplication Patterns

Content Modeling

Application patterns for reusable sections, relationships between records, and content structures that can grow with a project.

Use these patterns when the main question is how to shape the content itself. They help you decide how records relate to each other, how flexible a page should be, and how to avoid locking editors into brittle one-off structures.

Build flexible pages from reusable blocks

Problem: you want editors to build or rearrange page sections without turning each page into one giant object.

This pattern uses blocks to model page sections as reusable building blocks inside a page layout. It is the right starting point when you need flexible landing pages, marketing pages, or reusable content sections.

Example implementation

import { defineBlock, defineBlocksField, defineCollection, defineTextField, defineTextareaField, defineUrlField } from "@dyrected/core";

export const HeroBlock = defineBlock({
  slug: "hero",
  labels: { singular: "Hero", plural: "Heroes" },
  fields: [
    defineTextField({ name: "heading", label: "Heading", required: true }),
    defineTextareaField({ name: "body", label: "Body" }),
  ],
});

export const CallToActionBlock = defineBlock({
  slug: "callToAction",
  labels: { singular: "Call to action", plural: "Calls to action" },
  fields: [
    defineTextField({ name: "label", label: "Link label", required: true }),
    defineUrlField({ name: "url", label: "URL", required: true }),
  ],
});

export const Pages = defineCollection({
  slug: "pages",
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineBlocksField({
      name: "layout",
      label: "Page layout",
      blocks: [HeroBlock, CallToActionBlock],
    }),
  ],
});

Read the full docs:

Model a relationship and its reverse lookup

Problem: one record should point to another record, and you also want the reverse view without storing duplicate data.

This pattern stores the owning relationship on one side and uses a join field for the reverse lookup. It is a good fit for authors and posts, products and categories, or any one-to-many content structure.

Example implementation

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

export const Users = defineCollection({
  slug: "users",
  auth: true,
  fields: [
    defineTextField({ name: "name", label: "Name", required: true }),
    defineJoinField({
      name: "posts",
      label: "Posts",
      collection: "posts",
      on: "author",
      limit: 20,
    }),
  ],
});

export const Posts = defineCollection({
  slug: "posts",
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineRelationshipField({
      name: "author",
      label: "Author",
      relationTo: "users",
      required: true,
    }),
  ],
});

Read the full docs:

Create a site settings global

Problem: you need one shared place for site name, support details, and other values that should only exist once.

This pattern uses a global for singleton content instead of forcing editors to manage a one-row collection. It fits site settings, contact information, default CTA copy, and other values reused across many frontend surfaces.

Example implementation

import { defineEmailField, defineGlobal, defineTextField, defineUrlField } from "@dyrected/core";

export const SiteSettings = defineGlobal({
  slug: "site-settings",
  label: "Site settings",
  initialData: {
    siteName: "Acme Studio",
    supportEmail: "[email protected]",
  },
  fields: [
    defineTextField({ name: "siteName", label: "Site name", required: true }),
    defineTextField({ name: "tagline", label: "Tagline", defaultValue: "" }),
    defineEmailField({
      name: "supportEmail",
      label: "Support email",
      required: true,
    }),
    defineUrlField({
      name: "primaryCta",
      label: "Primary call to action",
    }),
  ],
});

Read the full docs:

Problem: editors need to manage shared navigation without hardcoding menus in the frontend.

This pattern models navigation as repeatable rows in a global, with one level of child links for grouped menus or dropdowns. It is a practical default for headers, footers, and docs navigation that should stay structured.

Example implementation

import { defineArrayField, defineGlobal, defineTextField, defineUrlField } from "@dyrected/core";

export const Navigation = defineGlobal({
  slug: "navigation",
  label: "Navigation",
  fields: [
    defineArrayField({
      name: "items",
      label: "Navigation items",
      fields: [
        defineTextField({ name: "label", label: "Label", required: true }),
        defineUrlField({ name: "link", label: "Link", required: true }),
        defineArrayField({
          name: "children",
          label: "Child links",
          fields: [
            defineTextField({ name: "label", label: "Label", required: true }),
            defineUrlField({ name: "link", label: "Link", required: true }),
          ],
        }),
      ],
    }),
  ],
});

Read the full docs:

Create a category taxonomy for content

Problem: entries need reusable categories so editors can organize content and build filtered listing pages.

This pattern stores categories in their own collection and relates content back to them with a has-many relationship field. It is a good fit for blogs, resources, case studies, and any content type that benefits from reusable taxonomy entries.

Example implementation

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

export const Categories = defineCollection({
  slug: "categories",
  admin: { useAsTitle: "title", urlPattern: "/categories/{slug}" },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", required: true, unique: true }),
  ],
});

export const Posts = defineCollection({
  slug: "posts",
  admin: { useAsTitle: "title", urlPattern: "/blog/{slug}" },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", required: true, unique: true }),
    defineRelationshipField({
      name: "categories",
      label: "Categories",
      relationTo: "categories",
      hasMany: 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