Content Engine

Defining a Content Type

Declare a Content Engine content type once to get a Postgres table, typed CRUD routes, and interactive AdminCP screens.

Content Engine generates a complete CRUD ecosystem from one declaration: a PostgreSQL table, Zod validation schemas, API endpoints, staff permissions, and AdminCP management screens.

Quick start

1. Define the Content Type

plugins/example/src/content/category.ts
import { defineContentType, field } from "@vitnode/core/content"

export const categoryContentType = defineContentType({
  id: "example.category",
  tableName: "example_categories",
  fields: {
    name: field.text({ required: true, minLength: 2, maxLength: 100 }),
    slug: field.slug({ from: "name", required: true }),
  },
})

2. Create the Database Model

plugins/example/src/database/categories.ts
import { createContentModel } from "@vitnode/core/content/server"
import { categoryContentType } from "@/content/category"

export const categoryContent = createContentModel(categoryContentType)
export const example_categories = categoryContent.table

3. Build & Run Migrations

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

Your table now exists in PostgreSQL.


Wire into API and AdminCP

1. Register CRUD Routes on the API

Add the model to your plugin's config.api.ts:

plugins/example/src/config.api.ts
import { buildApiPlugin } from "@vitnode/core/api/lib/plugin"
import { categoryContent } from "./database/categories"

export const exampleApiPlugin = () =>
  buildApiPlugin({
    pluginId: "@vitnode/example",
    contentTypes: [
      categoryContent,
    ],
  })

This immediately registers full CRUD routes under /api/@vitnode/example/admin/content/category.

2. Register Screens in the AdminCP

Connect the definition in src/config.tsx:

plugins/example/src/config.tsx
import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"
import { FolderIcon } from "lucide-react"
import { categoryContentType } from "./content/category"

export const examplePlugin = () =>
  buildPlugin({
    pluginId: "@vitnode/example",
    contentTypes: [
      contentTypeAdmin({
        definition: categoryContentType,
        icon: <FolderIcon />,
      }),
    ],
  })

3. Add Translations

Name the record in your locale file using ICU cardinal plural:

plugins/example/src/locales/en.json
{
  "@vitnode/example": {
    "content": {
      "category": {
        "label": "{count, plural, one {Category} other {Categories}}",
        "title": "Categories",
        "desc": "Organize posts into categories."
      }
    }
  }
}

4. Open the Management Screen

Visit http://localhost:3000/admin/content/example/category.

You have a complete AdminCP data table with search, sorting, pagination, and modal dialogs to create, edit, and delete records.


defineContentType Options

Prop

Type

Learn More