For Developers

A content layer that works the way your codebase does.

Schema in code. Type-safe client. Embedded deployment. No ops overhead. Dyrected fits into the way you already build — without forcing a separate service, a GUI schema builder, or a database you have to babysit.

Schema as code

Define collections, fields, validation rules, and access control in a single TypeScript config file. Commit it, review it, roll it back. Your schema is version-controlled like everything else in your project.

No GUI. No database migrations to write by hand. No schema drift between environments.

export default defineConfig({
  collections: [
    defineCollection({
      slug: 'posts',
      fields: [
        { name: 'title', type: 'text', required: true },
        { name: 'body', type: 'richText' },
        { name: 'author', type: 'relationship', collection: 'team' },
        { name: 'status', type: 'select',
          options: ['draft', 'published'] },
      ],
      access: {
        read: () => true,
        create: ({ user }) => user?.role === 'editor',
        update: ({ user }) => user?.role === 'editor',
        delete: ({ user }) => user?.role === 'admin',
      },
    }),
  ],
})

Embedded deployment

Dyrected mounts directly inside your Next.js or Nuxt app as a route handler. Push your site — the CMS comes with it. No extra subdomain, no second service to keep alive, no ops overhead added to the project.

The admin UI mounts automatically at /admin. Your existing infrastructure doesn't change.

// Next.js — app/cms/[...dyrected]/route.ts
import { handler } from '@dyrected/next'
import config from '../../../dyrected.config'

export const { GET, POST, PUT, DELETE } = handler(config)

Type-safe SDK

The SDK generates types directly from your schema. Your editor knows the shape of every collection, every field, every relationship. No casting. No guessing. No runtime surprises.

import { createClient } from '@dyrected/sdk'

const cms = createClient({
  baseUrl: process.env.CMS_URL,
  apiKey: process.env.API_KEY,
})

// Fully typed — fields, filters, relationships
const posts = await cms.collection('posts').find({
  where: { status: { equals: 'published' } },
  populate: ['author'],
  limit: 10,
})

Everything you need to model real content.

Field

What it does

text

Single-line string

textarea

Multi-line string

richText

Tiptap-powered rich text with links and images

number

Integer or float

boolean

Toggle

date

Date or datetime

select

Single choice from a defined list

multiSelect

Multiple choices

relationship

Link to another collection

array

Repeatable group of fields

object

Nested group of fields

blocks

Polymorphic content blocks (page builder)

image

File upload with auto-resize and blurhash

email

Validated email string

url

Validated URL string

json

Raw JSON blob

Access control

Define read, create, update, and delete access per collection — as a boolean, a function, or a JEXL expression string. Field-level access works the same way.

access: {
  read: () => true,
  update: ({ user, doc }) =>
    user?.id === doc.author || user?.role === 'admin',
  delete: ({ user }) => user?.role === 'admin',
}

Hooks

beforeChange, afterChange, beforeDelete, afterDelete — available at the collection and field level. Trigger webhooks, sync to external services, transform data, send notifications, or enforce invariants.

hooks: {
  afterChange: [
    async ({ doc, operation }) => {
      if (operation === 'create') {
        await notifySlack(
          `New post published: ${doc.title}`
        )
      }
    },
  ],
}

Auth collections

Mark any collection with auth: true and Dyrected adds email/password registration, login, JWT issuance, and token refresh automatically. Admin accounts and frontend user accounts are completely independent.

defineCollection({
  slug: 'customers',
  auth: true,
  fields: [
    { name: 'name', type: 'text' },
    { name: 'plan', type: 'select',
      options: ['free', 'pro'] },
  ],
})

Bring your own database and storage.

Switch adapters by swapping the package — no other changes required.

Database adapters

PostgreSQL@dyrected/db-postgres
MySQL@dyrected/db-mysql
MongoDB@dyrected/db-mongodb
SQLite@dyrected/db-sqlite

Storage adapters

AWS S3@dyrected/storage-s3
Backblaze B2@dyrected/storage-b2
Cloudinary@dyrected/storage-cloudinary
Local filesystem@dyrected/storage-local

The rest of the toolkit.

Blocks — polymorphic page builder

The blocks field type lets editors compose pages from named content groups in any order. Each block is its own schema. Editors drag, drop, and arrange. Your rendering layer maps block types to components.

{ name: 'sections', type: 'blocks',
  blocks: ['hero', 'features', 'cta'] }

Globals

Navigation, footer links, announcement banners, site settings — content that exists once, not as a list. Define it as a global with its own field schema. Editors update it through the same admin UI.

Live preview

Token-based preview mode lets editors see unpublished content rendered in the actual website. No separate preview environment required. Works with Next.js Draft Mode and any custom preview setup.

Open Source

Read it. Audit it. Self-host it.

Dyrected is open source under the Business Source License. The full source is public. Self-hosting is free for any purpose — including commercial client work.

Full source on GitHub
Self-host for free
Docker deployment
Managed cloud available
BSL license
No vendor lock-in

A CMS that fits in your repo, not beside it.

Schema in TypeScript. Deployed with your app. Types generated from your config.