Schema Definition
How Dyrected stores data, how your config maps to database columns, and how schema changes are managed without breaking migrations.
Your dyrected.config.ts is the single source of truth for everything — collections, globals, database adapter, storage, and email. Dyrected reads it at startup and derives the database schema, REST API, and Admin UI from it automatically.
How data is stored
Dyrected uses a JSON blob strategy by default. Each document is stored as a single row with an id column and a data JSON column. This means you can add, remove, or rename fields without running a migration — the schema is always in sync with your config.
The posts table holds one row per document — an id and a single data JSON column:
id | data (JSON) |
|---|---|
post-123 | { "title": "Hello", "status": "draft" } |
post-456 | { "title": "World", "status": "published" } |
The tradeoff: JSON fields cannot use native database indexes. For fields you filter or sort by frequently, you can opt out of the JSON blob.
Promoted fields
Setting promoted: true on a field extracts it from the JSON blob into its own SQL column with a native index. Queries on that field become significantly faster for large collections.
{
name: 'slug',
type: 'text',
unique: true,
promoted: true, // ← gets its own indexed SQL column
}After adding promoted: true, run npx dyrected sync:schema (Cloud) or restart your server (self-hosted) to create the column.
Supported types for promotion: text, number, boolean, date, datetime.
Good candidates for promotion: slug, status, publishedAt, email, createdAt — any field you where filter or sort by in list views.
Timestamps
By default, Dyrected injects createdAt and updatedAt into every document automatically. Both fields are promoted (indexed SQL columns).
defineCollection({
slug: 'posts',
timestamps: true, // default — can be set to false to omit
fields: [...],
})You do not need to define these fields yourself. They appear in API responses and are available in access functions and hooks.
Schema sync
Self-hosted projects sync automatically on startup. When your server starts, Dyrected inspects your config and ensures every promoted field and unique: true field has the correct column in the database. No manual step needed.
Dyrected Cloud requires an explicit sync because the database is remote:
npx dyrected sync:schemaRun this whenever you add a promoted: true field, add unique: true to an existing field, or change your database adapter. Regular field additions and removals (inside the JSON blob) do not require a sync.
MongoDB does not have a sync step. MongoDB is schema-less — collections and fields are created on first write.
There are no columns to add and no migration to run. renameTo and defaultValue still work exactly as described
above (applied on read), but you never need to run sync:schema with the MongoDB adapter.
Lazy field migrations
Renaming a field in your config would normally orphan existing data under the old key. Dyrected handles this with a lazy migration strategy using renameTo:
{
name: 'fullName', // the new name
type: 'text',
renameTo: 'name', // the old name to fall back on
}How it works:
- Read — if
fullNameis missing in a document, Dyrected readsnameinstead. - Write — on the next save, the value is written under
fullName. Over time, all documents migrate naturally without a bulk update.
Once all documents have been resaved under the new name, remove renameTo.
Seeding initial data
Use initialData to seed a collection or global the first time it is fetched and found to be empty. This is useful for default categories, config values, or demo content.
export const Categories = defineCollection({
slug: "categories",
initialData: [
{ name: "News", slug: "news" },
{ name: "Events", slug: "events" },
],
fields: [
{ name: "name", label: "Name", type: "text" },
{ name: "slug", label: "Slug", type: "text", promoted: true },
],
});initialData runs once — if the collection already has documents, it is skipped entirely.
System collections
Dyrected reserves slugs prefixed with __ for internal use:
| Slug | Purpose |
|---|---|
__admins | Conventionally used for CMS dashboard auth, separate from app users |
__audit | Stores audit log entries when audit: true is set on a collection |
You can define __admins as a regular auth collection in your config — the double-underscore prefix just signals that it is infrastructure, not application content.
Next steps
- Collections & Globals — the two building blocks and when to use each
- Fields — what fields are available and how they map to the database
- Relationships — how collections reference each other