Architecture Overview
A ChaiBuilder site is one Next.js application that plays two roles at once: it serves your public website, and it hosts the editing tools that produce that website. There is no separate backend, no vendor API in the request path, and no build step between editing and publishing.
If you hold one idea, hold this one: the builder is a client-side editing surface that talks to your own server, and your published pages are rendered by that same server from rows in your own database. Everything below is detail on that sentence.
The three layers
The product is built in layers, each extending the one below. Knowing which layer you are looking at explains most "can I do X?" questions.
| Layer | What it is | Where it lives |
|---|---|---|
| 1. Editor | A pure React component: blocks in, visual editing, blocks JSON out. It knows nothing about pages, servers, or databases. | chaicore |
| 2. Website builder | The editor plus everything a website needs: pages, SEO fields, publish flow, server wiring, page storage, and page rendering. | chaicore |
| 3. Pro | The website builder plus the Pro extensions - revisions, multilingual, redirects, roles, media management, advanced AI, and more. | chaipro |
Layer 1 is why "embed the builder in any React app" is true - the editor component in
chaicore has no server of its own. Layers 2 and 3 are what you get
when you want a whole website, which is what the starter ships. See
Open Source Core for the boundary in detail.
The four runtime surfaces
A deployed site exposes four distinct surfaces. They share a config, a database, and a request context, but they have very different characteristics.
Your Next.js app
│
├── / public site server-rendered, static, cached
├── /admin/editor visual builder client bundle, authenticated
├── /admin/api action endpoint server, authenticated, one route
└── /admin CMS admin Payload, authenticated
- Public site - a catch-all route that resolves a slug to a published page and renders it to HTML. Marked static, so it is built on a visitor's first request and then cached. It never reads draft content unless draft mode is on.
- Visual builder - a client-side application. It renders the editing canvas, panels, and the block library. It holds no database credentials and issues no SQL.
- Action endpoint - a single server route the builder posts to. Every builder operation (load a page, save blocks, upload an asset, publish, restore a revision) is a named action dispatched through a server-side registry, permission-checked against the current user.
- CMS admin - Payload's own admin panel for structured content. It is plain Payload, not a ChaiBuilder skin.
All four move together if you relocate /admin - see Admin Route.
Client vs server
The split is strict, and it is what keeps a visual editor safe to expose to non-technical editors.
Runs on the client (the builder bundle)
- The editing canvas, drag and drop, block tree, and settings panels
- Design token and theme editing, with a live preview of the result
- The AI chat surface and any custom panels or fields you register
- Nothing privileged: no connection string, no license key, no service credentials
Runs on the server
- Resolving the config and the request context: which site, which user, which permissions, draft or live, and the canonical site URL
- Every action, including all reads and writes to your database
- Data providers: global site data, page-type data, and per-block data
- Rendering published pages to HTML, including metadata, JSON-LD, and theme CSS
The practical consequence: a custom block's props panel is client code, while its data provider is server code. When something needs a secret or a query, it belongs on the server.
Configuration in four files
Configuration is deliberately split so that plain data stays importable by scripts, CI, and the Payload CLI, while per-request concerns stay in one place.
| File | Job |
|---|---|
next.config.ts |
Next.js integration and the generated import map |
chaibuilder.config.ts |
Server config as plain data: database, plugins, page types, providers, AI models |
chaibuilder.server.ts |
The per-request handle every server file imports |
payload.config.ts |
Payload CMS plus the ChaiBuilder plugin and schema hook |
Full option reference: Configuration.
Features are plugins
There is no feature-flag matrix. Registering a plugin turns its feature on, and the plugin's options carry its configuration:
plugins: [
revisionsPlugin({ drafts: true, maxRevisions: 10 }),
redirectsPlugin(),
multilingualPlugin(),
]
Most features have two halves: a server plugin (actions, permissions, data) and a client
plugin (the UI). You register the server half in chaibuilder.config.ts and pass the client
half to the builder component. Keep the two lists mirrored: a server plugin with no client
plugin has no UI, and a client plugin with no server plugin has UI that cannot save.
Because the two lists are explicit, a feature you do not register also does not ship in the editor bundle.
One database, two sets of tables
ChaiBuilder keeps its own tables for pages, site settings, assets, and plugin data. Payload keeps its collections. They live in the same database and, in the starter, share a single connection pool.
Your database
├── ChaiBuilder tables pages (draft + published), site settings, assets, plugin data
└── Payload collections blog, legal, media, users, and anything you add
A schema hook merges the ChaiBuilder tables into Payload's schema before migrations are generated, so one migration pipeline covers both. The full set of plugin tables is created whether or not the plugin is registered, which means enabling a feature later needs no migration.
One database can also host many sites. Each site is a row in the apps table with all of its
content scoped to it; a single-site deployment pins itself to one row with
CHAIBUILDER_APP_KEY.
Where you plug in
Because the deployment is your own Next.js app, extension points are ordinary code:
| You want to | You write |
|---|---|
| A new block type editors can drop on a page | A registered block: React component plus a props schema |
| Server data for that block | A block data provider in chaibuilder.config.ts |
| A page whose layout is designed once and reused per record | A page type with a data provider |
| A new operation the builder can call | A custom action, permission-checked like the built-ins |
| Extra UI in the builder | A registered panel, slot, or settings field |
| Anything else | A normal Next.js route, Payload collection, or server function |
See Custom Blocks and API.
Related
- How It Works - the same picture, one level up.
- Pages vs CMS - the two content models and how they meet.
- Blocks - the unit of composition inside every page.
- Page Types - one layout, many records.
- Global Blocks - content built once, used on many pages.
- Data Providers - how server data reaches a render.
- Rendering Model - how a published page becomes HTML.
- Data & Infrastructure Ownership - what lives where, and what leaves your server.
- Configuration - every option in the four files.

