Content Engine

Production & Security

Step-by-step guide to concurrency control, security enforcement, failure retries, performance scaling, and system limitations.

Deploying Content Engine models to production environments requires understanding concurrency protections, database security, performance indexing, and architectural boundaries.

Prerequisites & Context

Production hardening applies to both your client definition articleContentType and compiled server model articleContent:

  • Concurrency Locks: ContentVersionConflict is thrown when updating an editorial: { enabled: true } content type whose version has changed concurrently.
  • Staff Permissions: Enforced automatically by buildContentAdminModule for all management routes.
  • Diagnostics: contentEngineDiagnostics(c) checks registered content types, background effect queues, and search provider drift.

Step-by-Step Production Best Practices

Step 1: Handle Concurrency & Optimistic Lock Retries

When editorial: { enabled: true } is configured, update operations require passing the expected version integer. When a concurrent modification occurs, the engine throws ContentVersionConflict (which maps to HTTP 409 Conflict in generated routes):

import { ContentVersionConflict } from "@vitnode/core/content"

try {
  await service.update(articleId, { title: "New Title", version: 2 })
} catch (err) {
  if (err instanceof ContentVersionConflict) {
    // Current database version and expected version are available
    console.warn(`Conflict on item ${err.itemId}: current version is ${err.currentVersion}, expected ${err.expectedVersion}`)

    // Refetch latest record version and retry update
    const latest = await service.findById(articleId)
    if (latest && "version" in latest) {
      await service.update(articleId, {
        title: "New Title",
        version: latest.version,
      })
    }
  }
}

Step 2: Enforce Staff Permissions and Public Allowlists

Ensure all custom administrative actions verify staff permissions, and verify that publicApi.fields lists only non-sensitive attributes:

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

export const articleContentType = defineContentType({
  id: "example.article",
  tableName: "example_articles",
  publication: { enabled: true },
  publicApi: {
    enabled: true,
    path: "articles",
    fields: ["id", "title", "slug", "code", "excerpt"],
  },
  fields: {
    title: field.text({ required: true }),
    slug: field.slug({ source: "title" }),
    code: field.text({ required: true }),
    excerpt: field.textarea({ nullable: true }),
    internalNotes: field.textarea({ nullable: true }), // Private internal column
  },
})

internalNotes is omitted from publicApi.fields, preventing it from ever being exposed via public endpoints.

Step 3: Inspect Engine Diagnostics

Inspect engine health and drift in internal routes or monitoring probes:

import { contentEngineDiagnostics } from "@vitnode/core/content/server"

// Inside a Hono route handler:
const diagnostics = await contentEngineDiagnostics(c)

console.log(`Healthy: ${diagnostics.healthy}`)
console.log(`Search sync: ${diagnostics.searchHealthy}`)
console.log(`Background effects: ${diagnostics.effectsHealthy}`)
console.log(`Registered content types: ${diagnostics.contentTypes.length}`)

Architectural Boundaries & System Limitations

  • Code-First Architecture: Content types must be declared in TypeScript source control and compiled before migrations are generated. There is no runtime no-code UI for creating tables dynamically.
  • No Direct Schema Polymorphism: A single field cannot reference multiple unrelated tables. Use structured field groups or separate relation models instead.