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.

All of your site's content - pages, CMS collections, users, settings - lives in your own database. ChaiBuilder ships its own set of tables alongside Payload's, and both are managed through one migration pipeline, so there is a single database and a single upgrade path.
| Option | Best for | Connection |
|---|---|---|
| SQLite (local file) | Local development, quick starts | file:./local.db |
| libSQL / Turso | Small production sites, serverless-friendly SQLite | libsql://… URL + auth token |
| Postgres | Production, teams, multi-instance deployments | postgresql://… connection string |
The scaffolder (npx chaibuilder-app create) asks which one you want and writes the
connection into your environment, runs migrations, and seeds the database - so for a new
project there is nothing manual to do. The notes below are for changing databases later or
tuning production.
A local SQLite file is per-instance. On serverless platforms and multi-container deployments, each instance would get its own copy. Use a remote database (Turso or Postgres) for anything beyond a single long-running server.
The database connection is a single environment variable:
# Postgres
DATABASE_URL=postgresql://user:password@host:5432/dbname
# libSQL / Turso
DATABASE_URL=libsql://your-db.turso.io
Point this at your production database in your hosting platform's environment settings. The same variable is read by Payload and by ChaiBuilder's own tables - they share one connection.
ChaiBuilder opens one shared connection pool for the whole app (Payload and the builder share it, so you never pay for two pools). Pool sizing adapts to your provider automatically:
6543 or a ?pgbouncer=true
query param; the pool shrinks to 5 connections and shortens idle timeouts.On serverless platforms (many short-lived instances), always connect through your provider's pooler endpoint (Neon pooled connection string, Supabase transaction pooler on port 6543) rather than the direct database port, or you will exhaust connections under load.
To run ChaiBuilder inside a shared database, set an optional schema name and all tables are
created there instead of public:
CHAIBUILDER_POSTGRES_SCHEMA=chaibuilder
A single database can host any number of sites. Each site is a row in ChaiBuilder's
apps table, and every page, user membership, and asset is scoped to its site - so adding
a site is a data operation, not a new database or a new deployment. Which site a request
belongs to is resolved from the hostname, or pinned with CHAIBUILDER_APP_KEY for
single-site deployments (see
Environment & License).
This is what makes agency and portfolio setups cheap to run: one deployment, one database, one set of migrations - unlimited sites.
The starter uses Payload's migration workflow, and ChaiBuilder's tables are merged into the same schema snapshot - so one command manages everything:
# create a migration after changing collections or upgrading the SDK
pnpm payload migrate:create
# apply pending migrations
pnpm payload migrate
In production, the starter is configured with prodMigrations, which means pending
migrations run automatically when the app boots - a deploy with new migrations applies
them on startup. You can still run pnpm payload migrate explicitly as a release step if
you prefer migrations to happen before traffic switches over.
When you upgrade the ChaiBuilder SDK, the flow is the same as any Payload schema change: bump the package, run
pnpm payload migrate:create, review the generated migration, and deploy. See Changelog.
A fresh database gets its initial content (admin user, starter pages) from the seed script:
pnpm seed
The scaffolder runs this for you on project creation; you only need it when pointing the app at a brand-new empty database.
Starting on a local SQLite file does not lock you in:
DATABASE_URL to
the libsql:// URL and set the auth token.The database is yours, so backups are yours too. Use your provider's point-in-time recovery
(Neon, Supabase, RDS all offer it) or scheduled dumps (pg_dump) for self-hosted Postgres.
For SQLite, snapshot the file while the app is stopped, or use Turso's built-in backups.
DATABASE_URL fits in the full env.