Getting started
Quick start with Void
Mount the CMS router in a Void app, run it locally, and create the first admin.
Install the package next to Hono in a Void app:
bun add @interactive-inc/cms hono drizzle-orm zodMount the router
createCMS returns { engine, router }. Mount the router under /api behind a middleware that sets c.set("user", { id, role }). A Bearer token takes the API key path; otherwise the session from void/auth applies.
// src/lib/build-app.ts
import { createCMS, verifyApiKey, type CMSEnv } from "@interactive-inc/cms"
import { bearerToken } from "@interactive-inc/cms/api-key"
import { Hono } from "hono"
import { getUser } from "void/auth"
import { db } from "void/db"
import { storage } from "void/storage"
export function buildApp() {
return new Hono<CMSEnv>()
.basePath("/api")
.use("*", async (c, next) => {
const token = bearerToken(c.req.header("authorization"))
const user = token ? await verifyApiKey({ db, token }) : getUser()
c.set("user", user ? { id: user.id, role: "role" in user ? String(user.role) : "editor" } : null)
await next()
})
.route("/", createCMS({ db, bucket: storage }).router)
}Then hand every /api request to it from a Void route:
// routes/api/[...route].ts
import { defineHandler } from "void"
import { buildApp } from "@/lib/build-app"
const dispatch = defineHandler((c) => buildApp().fetch(c.req.raw, c.env, c.executionCtx))
export const GET = dispatch
export const POST = dispatch
export const PUT = dispatch
export const PATCH = dispatch
export const DELETE = dispatchcreateCMS is cheap, so build it per request when the D1 session is per request. On its first call the engine creates its own tables (cms_* and media) with CREATE ... IF NOT EXISTS and tracks its schema version in cms_meta. Do not add these tables to your own Drizzle schema or migrations.
Run it locally
Start the Vite dev server (bun run dev, or through portless for an https name). Void applies pending migrations and its auth tables on start. The reference app runs at https://cms.open.localhost.
Create the first admin
Sign-up is closed in the reference app. With OPEN_CMS_INIT_KEY set in .env and no users yet, sign up once with the key in the x-init-key header. The first account becomes admin.
curl -X POST https://cms.open.localhost/api/auth/sign-up/email \
-H "x-init-key: $OPEN_CMS_INIT_KEY" \
-H "content-type: application/json" \
-d '{"email":"you@example.com","password":"your password","name":"You"}'Sign in at /admin, invite other users from /admin/users, and issue an API key at /admin/api-keys to connect an AI agent (see MCP server).