HEADCODE CMS
Docs

Headcode CMS Developer Documentation

A single-page guide for developers and AI tools working with Headcode CMS: installation, configuration, sections, fields, admin UI, frontend rendering, Markdown output, MCP, authentication, images, and publishing.

Install commands

Use these snippets for fresh local projects. They are copyable command surfaces for humans and agents.

pnpm dlx shadcn@latest init --preset bbVJxYW --base base --template next --pointer
pnpm dlx shadcn@latest add headcodecms/headcodecms/headcode
pnpm install

Run Convex before auth setup so CONVEX_DEPLOYMENT and generated Convex files exist. Do not put server secrets into NEXT_PUBLIC_* variables.

Installation

The recommended path is agent-guided installation. The agent should read the installation contract, inspect the target repository, initialize the shadcn project if needed, install the Headcode registry item, run Convex setup, configure auth, and stop when secrets or project choices are required.

For a fresh app, Headcode CMS is installed through the shadcn registry. The registry item brings in the app routes, Convex backend, CMS configuration files, public section renderers, admin UI, MCP route, and supporting components.

Before you configure Convex Auth, decide:

  • which Convex project or deployment to use,
  • which admin emails are allowed,
  • which site URL auth should trust locally and in production,
  • whether MCP bearer tokens should be enabled,
  • and whether development test login should be enabled.

Run Convex before Auth setup so the generated Convex files and deployment environment exist. Never put server secrets into NEXT_PUBLIC_* variables.

Agent installation prompts

Use these prompts when asking Codex, Claude Code, ChatGPT, Claude, or another coding agent to install or modify Headcode CMS.

Read the Headcode CMS docs and source in this repository. Install or update Headcode CMS using the existing stack. Keep content modeling in headcode/config.ts and headcode/sections.ts, use Convex services for backend access, and verify with focused tests and lint.

Good prompts mention the exact surface being changed: content model, default content, frontend renderer, admin field, Convex service, MCP tool, auth, or deployment settings.

Project map

The main files are deliberately few and explicit.

  • headcode/config.ts defines collections and globals.
  • headcode/sections.ts defines available section types.
  • headcode/fields.ts defines reusable field factories and validators.
  • headcode/defaults.ts defines starter content.
  • headcode/types.ts contains inferred content types.
  • app/(site)/ renders the public website, Markdown views, sitemap, and /llms.txt.
  • app/(site)/_sections/ contains public section renderers. Keep HTML and Markdown rendering together here.
  • app/(admin)/ contains the authenticated editor UI.
  • app/(admin)/_fields/ maps field definitions to admin field controls.
  • app/(mcp)/[transport]/route.ts exposes MCP tools for authorized AI clients.
  • convex/services.ts is the shared service boundary used by site, admin, and MCP.
  • convex/schema_validators.ts contains shared Convex validators.
  • convex/section_validations.ts validates section JSON against configured field Zod validators.
  • convex/authorization.ts contains the default project authorization hooks.

When using an AI tool, point it to these files before asking it to change behavior. Most Headcode work is a small change across config, a section definition, a public renderer, and sometimes an admin field component.

The source code lives in headcodecms/headcodecms. These links are the best starting points when you want to inspect how Headcode works.

For most tasks, start with the folder link, then open the deep link only when you need the exact implementation.

Configuration

headcode/config.ts is the content model entry point. It describes which collections and globals exist and which sections each one may use.

A collection is repeatable. Use collections for docs pages, legal pages, blog posts, case studies, products, locations, or anything with many entries.

A global is a singleton. Use globals for home, header, footer, pricing, navigation, settings, or llms.txt.

The config is shared by the public site, admin UI, Convex validation, defaults, and MCP tool descriptions. Keep it boring and explicit. If a section is not listed for a collection or global, editors and agents should not add it there.

Configuration example

This is the shape a small documentation site might use. The public site fetches entries from Convex services and renders the configured section list.

import { code, footer, header, hero, llmsTxt, meta, snippet, text } from './sections'export const headcodeConfig = {  collections: [    {      slug: 'docs',      label: 'Docs',      path: '/docs',      sections: [meta, hero, text, snippet, code],    },    {      slug: 'pages',      label: 'Pages',      path: '/pages',      sections: [meta, hero, text],    },  ],  globals: [    {      slug: 'home',      label: 'Home',      path: '/',      sections: [meta, hero, text],    },    {      slug: 'header',      label: 'Header',      sections: [header],    },    {      slug: 'footer',      label: 'Footer',      sections: [footer],    },    {      slug: 'llms',      label: 'llms.txt',      path: '/llms.txt',      sections: [llmsTxt],    },  ],} as const

If you are adding a new content area, start in config, then add sections and renderers only when the existing ones are not enough.

Sections

A page is an ordered list of sections. Sections are the main building blocks of Headcode CMS content.

Examples of default sections include:

  • meta for title and description,
  • hero for the first page section,
  • text for Markdown rich text,
  • imageText for image and copy pairs,
  • image for standalone media,
  • snippet for copyable commands and prompts,
  • code for syntax-highlighted examples,
  • header and footer for layout globals,
  • and llmsTxt for editable /llms.txt content.

Each section has a name, label, icon, and fields. Section data is stored in Convex as a JSON string, then parsed and validated before services return it. This makes it possible for the admin UI, public site, and MCP tools to share one content model.

When creating a new section, think in four layers:

  • the editor fields in headcode/sections.ts,
  • default starter data in headcode/defaults.ts,
  • HTML rendering in app/(site)/_sections/,
  • Markdown rendering in the same section renderer file.

Keep new sections narrow. A good section represents one reusable page block, not an entire page hidden inside one field.

Section example

This example adds a small FAQ section. Notice that the field definition, public HTML, and Markdown output are all predictable for an AI tool to follow.

import { z } from 'zod'import { TextField } from './fields'export const faq = {  name: 'faq',  label: 'FAQ',  icon: 'circle-help',  fields: [    {      name: 'items',      label: 'Questions',      type: 'array',      schema: z.array(        z.object({          question: z.string().min(1),          answer: z.string().min(1),        }),      ),      fields: [        TextField({          name: 'question',          label: 'Question',        }),        TextField({          name: 'answer',          label: 'Answer',        }),      ],    },  ],} as const

After adding the renderer, register it in the site section map. If the section should be editable by agents, make sure its fields validate cleanly and have obvious labels.

Fields

Fields are the editable values inside sections. Headcode field helpers live in headcode/fields.ts and pair editor metadata with Zod validation.

Common field types include:

  • text and textarea fields,
  • rich text fields that store Markdown,
  • link fields with title, URL, and new-window behavior,
  • image fields that store image references,
  • select fields,
  • checkbox fields,
  • date/time fields,
  • and array fields for repeated structures.

Field validators matter because section data can come from the admin UI, defaults, or MCP tools. The same validation path should protect all of them.

Rich text stores Markdown. Images store references such as { imageId }; the image metadata, dimensions, blur placeholder, and storage IDs live in the Convex images table and are hydrated by services when needed.

Field examples

Use existing field helpers when possible. If a new field type is needed, also add its admin field component and keep the data shape easy for agents to produce.

import { LinkField, RichtextField, TextField } from './fields'export const callout = {  name: 'callout',  label: 'Callout',  icon: 'message-square',  fields: [    TextField({      name: 'eyebrow',      label: 'Eyebrow',    }),    RichtextField({      name: 'content',      label: 'Content',    }),    LinkField({      name: 'action',      label: 'Action',    }),  ],} as const

Prefer explicit field names over clever abstractions. Good labels help humans in the admin UI and help AI tools generate valid section JSON.

Admin UI

The admin UI lives in app/(admin)/. It is a focused editor for entries, sections, images, drafts, and publishing.

Editors can:

  • sign in with Convex Auth,
  • browse globals and collections,
  • add, update, duplicate, reorder, and delete sections,
  • edit fields through typed controls,
  • upload and manage images,
  • work on draft content,
  • publish draft changes to live,
  • create a new draft from live content,
  • and restore previous snapshots where supported.

The admin UI should call Convex services, not database helpers. The service boundary keeps validation, authorization, image hydration, version routing, and future MCP parity in one place.

For custom projects, the usual admin work is adding a field renderer in app/(admin)/_fields/ when a new field type is introduced. Most section changes only need existing fields and do not need admin UI code.

Frontend website building

The public website lives in app/(site)/ and uses the Next.js App Router. Server Components should fetch content through Convex service queries and keep fetching simple. Let Convex function caching and the Headcode version selector do the first layer of work before adding custom Next.js caching.

Public pages are assembled from ordered sections. The section renderer maps a section name to a React component and maps the same section data to Markdown output.

For a custom website:

  • keep shared layout chrome in site components,
  • keep section-specific HTML and Markdown in app/(site)/_sections/,
  • keep page-specific Markdown dispatchers next to their matching route as md.ts,
  • use the configured collection routes to fetch entries by slug at request time,
  • and avoid generateStaticParams for CMS-created pages unless static rendering is an intentional optimization.

The public site should feel like a real website, not a CMS preview. Headcode gives you defaults, but project-specific design belongs in the site layer.

Frontend section rendering

HTML and Markdown renderers should stay close together. Markdown should be rendered from validated section data, not scraped from rendered HTML.

import { TextSection, renderTextMarkdown } from './text'import { FaqSection, renderFaqMarkdown } from './faq'const sectionComponents = {  text: TextSection,  faq: FaqSection,}const sectionMarkdownRenderers = {  text: renderTextMarkdown,  faq: renderFaqMarkdown,}

If an AI tool adds a section, ask it to update both the visual renderer and the Markdown renderer. This is one of the most important Headcode conventions.

Markdown output and llms.txt

Headcode assumes every important public page should be understandable as Markdown.

The designed HTML page is for humans. The Markdown page is for AI tools, search-adjacent workflows, documentation ingestion, and quick source-of-truth reading.

The Markdown route should use the same dispatcher as ?md and Accept: text/markdown. It should call page-local md.ts renderers, and those renderers should call section Markdown functions.

/llms.txt is a CMS-backed global. It should explain what the site is, link to important Markdown pages, and describe how agents should use the site. Keep it concise, accurate, and updated when important routes or docs change.

Do not generate Markdown by scraping HTML. Use validated section data directly so the output stays stable, compact, and predictable.

Convex services and validation

Convex is the backend for Headcode CMS. The public site, admin UI, and MCP route all go through convex/services.ts.

This boundary is important. Services handle:

  • reading globals and collection entries,
  • ensuring default globals exist,
  • creating and renaming entries,
  • adding, updating, duplicating, deleting, and reordering sections,
  • image registration and metadata updates,
  • draft/live publishing operations,
  • version-aware content fetching,
  • authorization checks,
  • and parsed, validated section data.

Shared validators live in convex/schema_validators.ts. Section JSON validation lives in convex/section_validations.ts. Do not duplicate schema shapes across services, database helpers, and MCP tools.

Section data is stored as JSON strings in Convex. Services return parsed and validated data. This keeps storage flexible while still giving the frontend and agents a safe typed shape.

MCP

Headcode includes an MCP server so authorized AI clients can inspect and edit CMS content without pretending to use the browser.

The MCP route lives at app/(mcp)/[transport]/route.ts. Access is controlled with bearer tokens. Configure matching ALLOWED_MCP_TOKENS in Convex and in the Next.js environment.

Typical MCP tools include:

  • headcode_get_version,
  • headcode_list_entries,
  • headcode_get_entry,
  • headcode_list_section_types,
  • headcode_ensure_global,
  • headcode_add_entry,
  • headcode_rename_entry,
  • headcode_delete_entry,
  • headcode_update_section,
  • headcode_add_section,
  • headcode_duplicate_section,
  • headcode_delete_section,
  • headcode_reorder_sections,
  • headcode_upload_image_from_url,
  • headcode_create_image_upload_url,
  • headcode_register_uploaded_image,
  • headcode_list_images,
  • headcode_get_image_usage,
  • headcode_update_image_metadata,
  • headcode_delete_image,
  • headcode_new_draft,
  • and headcode_publish.

Publishing is intentionally special. Agents should not call headcode_publish unless the user explicitly asks to publish, release, promote draft to live, or make draft content public.

MCP prompts

Use prompts that separate inspection, editing, image upload, and publishing. This reduces accidental live changes.

Use the Headcode MCP server. Call headcode_get_version, list entries, then read the docs entry. Summarize the current content model and do not make changes.

For local binary image uploads, create an upload URL, POST the file, then register the uploaded image. For remote images, prefer headcode_upload_image_from_url because it validates and creates metadata server-side.

MCP client configuration

Exact client config differs between tools, but the important parts are the endpoint URL and bearer token.

{  "mcpServers": {    "headcode-draft": {      "url": "https://draft.example.com/mcp",      "headers": {        "Authorization": "Bearer hc_dev_token"      }    },    "headcode-live": {      "url": "https://example.com/mcp",      "headers": {        "Authorization": "Bearer hc_live_token"      }    }  }}

Live and draft MCP clients should usually point to different hosts. Version routing follows the request host; do not invent separate service arguments for live versus draft.

Authentication

Admin authentication uses Convex Auth with Resend magic links by default.

The core production variables are:

  • JWT_PRIVATE_KEY,
  • JWKS,
  • SITE_URL,
  • AUTH_RESEND_KEY,
  • optional AUTH_RESEND_FROM,
  • and ALLOWED_ADMIN_EMAILS.

ALLOWED_ADMIN_EMAILS is the default project-level allow list. If a user signs in with an email that is not allowed, access should fail with a friendly UI message rather than a raw server stack trace.

Development test login can be enabled, but keep it out of production. It requires matching public and server-side variables, including NEXT_PUBLIC_HEADCODE_ENABLE_TEST_LOGIN, NEXT_PUBLIC_HEADCODE_ADMIN_TEST_TOKEN, HEADCODE_ENABLE_TEST_LOGIN, HEADCODE_ADMIN_TEST_TOKEN, and HEADCODE_ADMIN_TEST_EMAIL. The test email still needs to be in ALLOWED_ADMIN_EMAILS.

Authorization extension points live in convex/authorization.ts. Extend this file for project-specific users, organizations, roles, metadata, or stricter publishing rules.

Draft, live, and publishing

Headcode has draft and live versions.

Editors and agents normally modify draft content. Publishing promotes draft content to live and creates a new draft from it. This gives teams a controlled path from editing to public release.

Version selection is configured with NEXT_PUBLIC_HEADCODE_VERSION:

  • live always reads live content,
  • draft always reads draft content,
  • auto uses NEXT_PUBLIC_HEADCODE_DRAFT_HOSTS to choose draft hosts and otherwise defaults to live.

Use auto for production-like setups where preview or staging hosts should read draft content while the main public host reads live content.

For AI tools, the rule is simple: inspect and edit drafts freely when authorized, but publish only after an explicit user request.

Environment example

Keep browser-safe values public and secrets server-side. Convex environment variables are set with pnpm convex env set, not by adding them to a client bundle.

NEXT_PUBLIC_SITE_URL=http://localhost:3000NEXT_PUBLIC_HEADCODE_VERSION=autoNEXT_PUBLIC_HEADCODE_DRAFT_HOSTS=headcode.localhostALLOWED_MCP_TOKENS=hc_dev_token

Use different tokens for local, draft, and production contexts. Rotate tokens if they are shared with the wrong client or appear in logs.

Images

Images are stored in Convex Storage and referenced from section data with an imageId.

The image table stores metadata such as filename, content type, dimensions, storage ID, alt text, and blur placeholder data. Services hydrate image fields so public renderers can use the image safely.

For admin users, the image manager handles upload and metadata editing. For MCP clients, prefer headcode_upload_image_from_url when the source is a URL. It validates the image, generates metadata and blur data server-side, stores the asset in Convex, and returns { imageId }.

If an MCP client must upload a local binary, the flow is:

  1. call headcode_create_image_upload_url,
  2. upload the file with HTTP POST to the returned URL,
  3. call headcode_register_uploaded_image with the returned storageId,
  4. update the target section with the new imageId.

Always include useful alt text. It improves accessibility, search context, and agent-readable page summaries.

Manual developer workflow

For most Headcode development tasks, use this loop:

  1. Read AGENTS.md and ARCHITECTURE.md if the change touches more than one app area.
  2. Inspect headcode/config.ts to understand allowed collections, globals, and section types.
  3. Inspect headcode/sections.ts and headcode/fields.ts before adding data shapes.
  4. Add or update default content in headcode/defaults.ts when the starter site should change.
  5. Add public HTML and Markdown renderers in app/(site)/_sections/.
  6. Add admin field components only when introducing a new field type.
  7. Keep backend access behind convex/services.ts.
  8. Run focused tests, lint, and registry validation when relevant.
  9. Read the Markdown output as a final agent-facing verification pass.

Good success criteria are concrete: the content validates, the public page renders, the Markdown output is readable, the admin UI can edit the data, MCP tools can describe or update it, and publishing remains explicit.

Developer task prompts

These prompts are useful when working with a coding agent on an existing Headcode project.

Add a testimonials section to this Headcode CMS project. Use existing field helpers, add the section to the correct collections in headcode/config.ts, create default content if useful, render HTML and Markdown in app/(site)/_sections, and verify section validation.

Agents work best when the prompt states the boundary, the files to inspect, the desired output, and whether publishing is allowed.

What a new developer should understand

Headcode CMS is small on purpose. The architecture is meant to be read, changed, and explained by developers and agents.

The most important mental model is:

  • config says what content may exist,
  • sections say what data each content block stores,
  • fields say how that data is edited and validated,
  • defaults provide starter content,
  • Convex services protect the backend boundary,
  • site renderers turn validated data into HTML and Markdown,
  • admin UI gives humans a focused editor,
  • MCP gives authorized agents a direct editing interface,
  • and draft/live publishing controls when changes become public.

If you keep those boundaries intact, Headcode is straightforward to customize. Add the content shape, render it for humans, render it for agents, validate it at the service boundary, and publish only when the user intends to go live.