Content Engine

Database & Migrations

How the Content Engine maps content models to PostgreSQL tables using createContentModel, handles system columns, and executes Drizzle Kit migrations.

Content Engine content types compile directly into standard PostgreSQL database tables via Drizzle ORM.

Prerequisites & Context

Before setting up database tables, you must have a content type definition (e.g. articleContentType) created using defineContentType in src/content/article.ts:

src/content/article.ts
import { defineContentType, field } from '@vitnode/core/content'

// 1. Client-safe definition (imported by both API and AdminCP)
export const articleContentType = defineContentType({
  id: 'example.article',
  tableName: 'example_articles',
  fields: {
    title: field.text({ required: true }),
    code: field.text({ required: true }),
  },
})
  • createContentModel(definition, options): A backend utility from @vitnode/core/content/server that converts a client-safe content definition into a server-side Drizzle ORM model.
  • articleContent: The compiled model object containing .table (raw Drizzle table), .service(c) (CRUD helper), and .schemas (Zod schemas).

Step-by-Step Database Setup

Step 1: Create Database Model File

Create your model file under src/database/articles.ts. Import createContentModel from @vitnode/core/content/server and your definition articleContentType:

src/database/articles.ts
import { createContentModel } from '@vitnode/core/content/server'
import { articleContentType } from '@/content/article'
import { example_categories } from './categories'

// Compile client definition into a server Drizzle model
export const articleContent = createContentModel(articleContentType, {
  references: {
    category: () => example_categories.id,
  },
})

// Export raw Drizzle table for migration discovery
export const example_articles = articleContent.table

Step 2: Add Database Indexes

Add single or composite indexes inside your content definition file src/content/article.ts:

src/content/article.ts
export const articleContentType = defineContentType({
  id: 'example.article',
  tableName: 'example_articles',
  fields: {
    title: field.text({ required: true }),
    code: field.text({ required: true }),
    status: field.enum({ values: ['draft', 'published'] }),
  },
  indexes: [
    { on: ['status', 'createdAt'] }, // Composite index
    { on: ['code'], unique: true }, // Unique constraint
  ], 
})

Step 3: Run Database Migrations

Compile your plugins and apply schema changes:

bun run build:plugins && bun run db:migrate
pnpm build:plugins && pnpm db:migrate
npm run build:plugins && npm run db:migrate

Automatic System Columns

When createContentModel compiles a model, it automatically includes standard system columns:

ColumnPostgres TypeDescription
idserial / integerPrimary key identifier
createdAttimestampAuto-populated creation timestamp
updatedAttimestampAuto-updated modification timestamp

If publication is enabled, status and publishedAt columns are added.
If editorial is enabled, a version column is added for optimistic concurrency control.