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.

The Pro SDK ships as chaipro with purpose-specific entry points. The rule
that keeps imports correct: client APIs come from the root and /registry; server APIs
come from /nextjs/server, /nextjs/render, /payload, and /db/*. Importing the root
package in a server context throws by design.
| Entry point | Side | Purpose |
|---|---|---|
chaipro |
client | Builder component + UI extension APIs (panels, slots, hooks, flags) |
chaipro/registry |
client | Block registration - registerChaiBlock, props schemas, fonts |
chaipro/web-blocks |
client | loadWebBlocks() - the built-in block library |
chaipro/types |
shared | All public types |
chaipro/utils |
shared | HTML-to-blocks conversion, design token helpers |
chaipro/nextjs |
server | withChaiBuilder next.config wrapper |
chaipro/nextjs/server |
server | Config, context, actions, permissions |
chaipro/nextjs/render |
server | RenderChaiBlocks, ChaiPageCSS, styles utilities |
chaipro/nextjs/render-client |
client | PreviewBanner, ChaiAnimationProvider |
chaipro/db/* |
server | Database adapter factories (postgres, node-pg, libsql, d1, better-sqlite3) |
chaipro/payload |
server | Payload integration - plugin, schema hook, createPayloadChaiBuilder |
chaipro/plugins/<name>/server and /client |
per plugin | Feature plugins (redirects, revisions, trash, roles, media, ...) |
chaipro/ui/* |
client | shadcn-based UI primitives for custom panels |
All registration APIs are client-only and must run at module level before the builder mounts - centralize them in one setup file. The main families:
| API | What it does |
|---|---|
registerChaiBlock(component, config) |
add a custom block (from /registry) |
registerChaiBlockProps(schema) |
define a block's editable props schema |
registerChaiSidebarPanel(id, options) |
add a sidebar panel |
registerChaiSlot(CHAI_SLOT_IDS.X, Component) |
inject UI into a named slot |
registerChaiHook(CHAI_HOOKS.X, fn) |
intercept lifecycle events (before/after save) |
registerChaiFeatureFlag(key, options) |
user-toggleable flags |
registerChaiLibrary(id, options) |
add a browsable block library |
registerChaiAddBlockTab(id, options) |
add a tab to the Add Block drawer |
Hooks for reading builder state from custom UI include useChaiAuth,
useUserPermissions, useActivePage, useSavePage, useChaiFeatureFlag, and
useWebsitePages. Full walkthroughs: Extending the UI
and Custom Blocks.
getChaiBuilder (exported from your chaibuilder.server.ts, see
Configuration) returns a request-scoped instance with all
server APIs bound to the caller's context:
const cb = await getChaiBuilder(props) // pages/layouts
const cb = await getChaiBuilder(props, request) // route handlers
| Method | Returns |
|---|---|
cb.getPagePayload(slug) |
full render payload - { page, pageData, settings, pageProps } |
cb.generateMetaData(...) |
a Next.js Metadata object for the page |
cb.getPageData(slug) |
resolved data-provider output for a page |
cb.getSiteSettings() |
site-wide settings |
cb.getSiteGlobalData() |
output of your globalDataProvider |
cb.getPages() |
all pages |
cb.handleHttpAction(body) |
dispatch a builder action from a route handler |
The instance is request-scoped - never cache it or share it across requests.
All client-to-server builder communication goes through one route, which the starter already includes:
// app/(builder)/api/chai/route.ts
export async function POST(request: NextRequest, props: ChaiBuilderRouteProps) {
const cb = await getChaiBuilder(props, request)
return cb.handleHttpAction(await request.json())
}
The body is { action: "ACTION_NAME", data: {...} }. Authentication comes from the Payload
auth cookie (builder UI) or an Authorization: Bearer header (programmatic calls), and
each action enforces its required permission before executing. After an action runs, any
cache tags and paths it reports are revalidated automatically - see
Caching & Revalidation.
Add your own actions by extending ChaiBaseAction:
import { ChaiBaseAction } from 'chaipro/nextjs/server'
import { z } from 'zod'
export class GetInventoryAction extends ChaiBaseAction {
protected getValidationSchema() {
return z.object({ productId: z.string() })
}
async execute(data: { productId: string }) {
const { appId, userId } = this.context! // set before execute()
return fetchInventory(appId, data.productId)
}
}
Register it in chaibuilder.config.ts under actions: { GET_INVENTORY: new GetInventoryAction() }, then call it from the builder UI through the action endpoint, or
server-side with dispatchChaiAction("GET_INVENTORY", data) (throws on error) or
tryChaiAction(...) (returns { ok, data | error }).
Server components for rendering published pages, from /nextjs/render:
RenderChaiBlocks - renders a page payload; accepts optional linkComponent,
imageComponent, buttonComponent overrides.ChaiPageCSS - page CSS (Tailwind output, theme variables, fonts); must be in
<head>.ChaiPageJSONLD - structured data for SEO.getStylesForBlocks, getChaiThemeCssVariables - lower-level utilities for
custom render pipelines.The permission catalog is CHAI_PERMISSIONS (core keys) plus keys registered by active
plugins - getPermissionCatalog() returns the runtime set. Check access in custom code
with hasPermission, and resolve a user's effective set with resolvePermissions.
Effective permissions are the user's grants intersected with any credential-level
delegatedPermissions ceiling. See
Roles & Permissions.
