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.tsdefines collections and globals.headcode/sections.tsdefines available section types.headcode/fields.tsdefines reusable field factories and validators.headcode/defaults.tsdefines starter content.headcode/types.tscontains 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.tsexposes MCP tools for authorized AI clients.convex/services.tsis the shared service boundary used by site, admin, and MCP.convex/schema_validators.tscontains shared Convex validators.convex/section_validations.tsvalidates section JSON against configured field Zod validators.convex/authorization.tscontains 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.
Source links
The source code lives in headcodecms/headcodecms. These links are the best starting points when you want to inspect how Headcode works.
- Repository root
- Installation contract
- Architecture notes
- CMS configuration folder
- CMS config
- Section definitions
- Field definitions
- Default content
- Public site routes
- Public section renderers
- Admin app
- Admin field components
- MCP route
- Convex backend
- Convex services
- Section data validation
- Authorization hooks
- Registry definition
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 constIf 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:
metafor title and description,herofor the first page section,textfor Markdown rich text,imageTextfor image and copy pairs,imagefor standalone media,snippetfor copyable commands and prompts,codefor syntax-highlighted examples,headerandfooterfor layout globals,- and
llmsTxtfor editable/llms.txtcontent.
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 constAfter 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 constPrefer 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
generateStaticParamsfor 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:
livealways reads live content,draftalways reads draft content,autousesNEXT_PUBLIC_HEADCODE_DRAFT_HOSTSto 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_tokenUse 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:
- call
headcode_create_image_upload_url, - upload the file with HTTP
POSTto the returned URL, - call
headcode_register_uploaded_imagewith the returnedstorageId, - 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:
- Read
AGENTS.mdandARCHITECTURE.mdif the change touches more than one app area. - Inspect
headcode/config.tsto understand allowed collections, globals, and section types. - Inspect
headcode/sections.tsandheadcode/fields.tsbefore adding data shapes. - Add or update default content in
headcode/defaults.tswhen the starter site should change. - Add public HTML and Markdown renderers in
app/(site)/_sections/. - Add admin field components only when introducing a new field type.
- Keep backend access behind
convex/services.ts. - Run focused tests, lint, and registry validation when relevant.
- 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.