The Visual Website Builder
You Actually Own. React + Next.js, low-code, self-hosted. Your data, your infrastructure, every feature included.
© ChaiBuilder. All rights reserved.

A ChaiBuilder project is configured in four files, each with one job. Environment variables (database, secrets, license) are covered separately in Environment & License.
| File | Job |
|---|---|
next.config.ts |
Next.js integration |
chaibuilder.config.ts |
The builder's server config - plain data, importable anywhere |
chaibuilder.server.ts |
Per-request context - auth, tenant, draft state |
payload.config.ts |
Payload CMS + the ChaiBuilder Payload plugin |
next.config.tsWrap your Next.js config with the ChaiBuilder wrapper (the Payload starter composes it with
withPayload):
import { withChaiBuilder } from 'chaipro/nextjs'
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {}
export default withChaiBuilder(nextConfig, {
collections: 'src/collections/**/*.{ts,tsx}', // default
actions: 'src/app/(builder)/**/actions/**/*.{ts,tsx}', // default
importMapPath: 'src/chaibuilder-import-map.ts', // default
watch: true, // dev file watching
})
The wrapper scans your collections and actions and generates an import map at startup (and on file changes in dev). The generated file is auto-imported - never edit it by hand.
chaibuilder.config.tsThe main server config, created with buildChaiBuilderConfig. It is deliberately plain
data with no framework imports, so scripts, CI, and the Payload CLI can import it.
import 'server-only'
import { buildChaiBuilderConfig } from 'chaipro/nextjs/server'
export default buildChaiBuilderConfig({
db, // required - database connection (see adapters below)
plugins: [...], // registered plugins define the feature set
globalDataProvider, // data merged into every page render
pageTypes: [...], // dynamic page types (blog posts, docs, ...)
collections: [...], // collection/repeater configs
blockDataProviders: {}, // per-block-type server data
actions: {}, // custom server actions
ai: { models: [...] }, // AI models offered in the builder
debugLevel: 0, // 0 = off | 1 = DB+HTTP | 2 = full timing+SQL
})
| Field | Required | Description |
|---|---|---|
db |
yes | Database connection from an adapter factory |
plugins |
- | Feature set: a registered plugin turns its feature on; plugin options carry its config. Nothing is auto-installed |
globalDataProvider |
- | Async provider merged into every render ({ lang, draft, inBuilder }) |
pageTypes |
- | Page types with data providers; on Payload, adapt a collection directly |
collections |
- | Collections exposed to the builder |
blockDataProviders |
- | { BlockType: async provider } resolved per block instance |
actions |
- | Custom actions keyed by UPPER_SNAKE_CASE name |
ai |
- | Models list: { id, name, provider, multiplier, description } per model |
debugLevel |
- | 0, 1, or 2 - server-side cache/DB/HTTP logging |
A second argument accepts onInit (runs once on first use, e.g. migrations) and extend
(arbitrary values accessible on the resolved config).
Import the factory matching your database and pass its result as db:
| Adapter | Import | Use |
|---|---|---|
| Postgres (postgres.js) | chaipro/db/postgres |
hosted/self-managed Postgres |
| Postgres (node-pg pool) | chaipro/db/node-pg |
share one pg.Pool with Payload |
| libSQL / Turso | chaipro/db/libsql |
remote SQLite |
| SQLite (file) | chaipro/db/better-sqlite3 |
local development, single server |
| Cloudflare D1 | chaipro/db/d1 |
Workers deployments |
The Payload starter uses createNodePgDB({ pool }) with a shared pool so Payload and the
builder use one connection pool - see
Database Setup.
Features ship as plugins - redirects, revisions, trash, multilingual, media, roles, and more. Register server plugins in this config and their client counterparts on the builder component. Fine-grained config goes in the plugin's options:
plugins: [
revisionsPlugin({ drafts: true }),
redirectsPlugin(),
mediaPlugin({ storage }),
]
chaibuilder.server.tsCreates the request-scoped entry point every server file imports. On Payload, one line derives the whole request context (identity, tenant, draft, site URL) from Payload:
import config from '@/chaibuilder.config'
import { createPayloadChaiBuilder } from 'chaipro/payload'
export const { getChaiBuilder } = createPayloadChaiBuilder(config)
Builder access comes from the user's app membership - a Payload login by itself grants no
builder access. For non-Payload auth, use createChaiBuilder(config, { context }) and
resolve { appId, userId, permissions, draft, siteUrl } yourself - see
API for the context contract.
payload.config.tsStandard Payload config plus two ChaiBuilder touch points:
import { chaiBuilderPlugin, chaiBuilderSchemaHook } from 'chaipro/payload'
export default buildConfig({
db: postgresAdapter({
beforeSchemaInit: [chaiBuilderSchemaHook], // merges ChaiBuilder tables into
// Payload's schema so one migration pipeline covers both
}),
plugins: [
chaiBuilderPlugin({
menus: true, // menus collection for nav building
revalidateCollections: ['blog'], // publish here revalidates pages
appCollections: ['blog', 'media'], // collections scoped per app/tenant
}),
],
})
chaiBuilderPlugin option |
Description |
|---|---|
menus |
adds a menus collection editable from the builder |
revalidateCollections |
collection slugs whose changes revalidate dependent pages - see Caching & Revalidation |
appCollections |
collections scoped to the app/tenant |
