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
textSingle-line string
textareaMulti-line string
richTextTiptap-powered rich text with links and images
numberInteger or float
booleanToggle
dateDate or datetime
selectSingle choice from a defined list
multiSelectMultiple choices
relationshipLink to another collection
arrayRepeatable group of fields
objectNested group of fields
blocksPolymorphic content blocks (page builder)
imageFile upload with auto-resize and blurhash
emailValidated email string
urlValidated URL string
jsonRaw 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
@dyrected/db-postgres@dyrected/db-mysql@dyrected/db-mongodb@dyrected/db-sqliteStorage adapters
@dyrected/storage-s3@dyrected/storage-b2@dyrected/storage-cloudinary@dyrected/storage-localThe 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.
A CMS that fits in your repo, not beside it.
Schema in TypeScript. Deployed with your app. Types generated from your config.