Content Engine

Relations & Advanced Modeling

Connect content types with foreign keys, junction tables, field groups, and repeatable child records.

Content Engine uses real relational modeling rather than unstructured JSON blobs. To-many relations generate indexed junction tables, repeatables generate child tables with foreign keys, and groups map to structured columns.

Quick start: To-One Relation

Connect an article to a single category:

1. Declare the Field

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

export const articleContentType = defineContentType({
  id: "blog.article",
  tableName: "blog_articles",
  fields: {
    title: field.text({ required: true }),
    category: field.relation({
      required: true,
      onDelete: "restrict",
      target: () => categoryContentType,
    }),
  },
})

2. Connect Drizzle References

plugins/blog/src/database/articles.ts
import { createContentModel } from "@vitnode/core/content/server"
import { articleContentType } from "../content/article"
import { blog_categories } from "./categories"

export const articleContent = createContentModel(articleContentType, {
  references: {
    category: () => blog_categories.id,
  },
})

Relation Shapes

ShapeDeclarationDatabase Structure
To-Onefield.relation({ target })Foreign key column on table
To-Manyfield.relation({ target, multiple: true })Auto-generated junction table
Self-Referencingfield.relation({ self: true })FK to same table (e.g. parent category)
User Relationfield.user({ multiple?: boolean })FK / junction to core_users
File Relationfield.file({ multiple?: boolean })FK / junction to core_files

To-Many Relations & Junction Tables

Support multi-category selection or ordered co-authors:

plugins/blog/src/content/post.ts
fields: {
  categories: field.relation({
    multiple: true,
    min: 1, // Enforce at least 1 selected
    target: () => categoryContentType,
  }),
  authors: field.user({
    multiple: true,
    ordered: true, // Preserves drag-and-drop order
  }),
}

Field Groups (field.group)

Group related fields into a single nested object:

fields: {
  seo: field.group({
    fields: {
      metaTitle: field.text({ maxLength: 70 }),
      metaDescription: field.textarea({ maxLength: 160 }),
      noIndex: field.boolean({ defaultValue: false }),
    },
  }),
}

Columns are created as seo_meta_title, seo_meta_description, and seo_no_index on the main table.


Repeatable Child Rows (field.repeatable)

Create structured arrays backed by a real child table:

fields: {
  faqItems: field.repeatable({
    min: 1,
    max: 10,
    fields: {
      question: field.text({ required: true }),
      answer: field.textarea({ required: true }),
    },
  }),
}

field.relation Options

Prop

Type

Learn More