ChaiBuilder Logo

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.

© ChaiBuilder. All rights reserved.