Give your users a website builder, inside your product
@charisol/plexo-sdk is a visual page builder you add as a React component. People who aren't designers build and edit pages section by section, and you keep the pages in your own database and turn them into clean HTML or React whenever you need.
A visual builder as a React component
Add one component and your users get a full page builder inside your own product. With no setup it opens a blank page and works offline.
Section by section, for non-designers
Pages are built from ready-made sections (hero, pricing, FAQ and more) that people edit by clicking and typing. Designers can still go free-form.
Brand and design system in sync
Colours, fonts and shapes live in one design system, and the Branding panel can keep it in step with Strata.
Clean HTML or React out
Compile any page to a standalone HTML document on your server, or to React code for an app. Neither needs the editor.
AI that edits the site
Describe the site, apply a model's suggested changes safely with applyCommands, and show your users a plain-language summary.
Your server, your data
Forms post to your own address, pages are saved where you choose, and no provider key ever lives in the browser.
1. Install
npm install @charisol/plexo-sdk react react-dom
2. Add the editor
Everything is optional. With no props at all you get a complete builder on a blank page. Pass pages to open an existing design, onSave to store it, formAction to send form messages to your server, and featuresto hide panels your product doesn't need.
'use client';
import { useRef } from 'react';
import { V2Editor, type V2EditorRef, type PageDefinition } from '@charisol/plexo-sdk/v2';
import '@charisol/plexo-sdk/dist/plexo-sdk.css';
export function SiteEditor({ pages }: { pages?: PageDefinition[] }) {
const editor = useRef<V2EditorRef>(null);
return (
<div style={{ height: '100vh' }}>
<V2Editor
ref={editor}
pages={pages} // omit to start on a blank page
autoSave
onSave={async (all) => {
await fetch('/api/site', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
pages: all,
designSystem: editor.current?.getDesignSystem(),
}),
});
}}
formAction={(formId) => `/api/forms/${formId}`} // forms post to your server
features={{ codeView: false, viewSwitch: false }} // keep the screen minimal
/>
</div>
);
}3. Next.js: load it in the browser
The editor uses the browser, so load it with next/dynamic and ssr: false. Import the stylesheet once, as in step 2. Its styles are scoped to the editor, so nothing leaks into your app.
// app/editor/page.tsx
'use client';
import dynamic from 'next/dynamic';
const SiteEditor = dynamic(
() => import('@/components/site-editor').then((m) => m.SiteEditor),
{ ssr: false, loading: () => <p>Loading the editor…</p> },
);
export default function EditorPage() {
return <SiteEditor />;
}4. Use your own pages
You decide how pages are stored and where they live. Hand the editor your list with pageSource and it shows a page switcher, then asks you to open, create, rename or delete. Leave it out and the editor keeps a simple list itself.
import { V2Editor } from '@charisol/plexo-sdk/v2';
<V2Editor
pages={current ? [current] : undefined}
pageSource={{
pages: mySite.pages.map((p) => ({ id: p.id, name: p.title, route: p.path })),
currentId: current?.id,
onSelect: (id) => router.push(`/editor/${id}`),
onCreate: ({ name, templateId }) => createPage(name, templateId),
onRename: (id, name) => renamePage(id, name),
onDelete: (id) => deletePage(id),
}}
/>5. Turn pages into HTML on your server
@charisol/plexo-sdk/v2/compile has no React or editor in it, so it runs anywhere on your backend. Compile to React code instead with compilePageToReact. Always check saved designs with parsePages, and run the HTML through sanitizeV2Html before you serve it.
import {
compilePageToHtml,
parsePages,
sanitizeV2Html,
type DesignSystem,
} from '@charisol/plexo-sdk/v2/compile';
export function renderPage(saved: unknown, designSystem?: DesignSystem) {
const parsed = parsePages(saved); // checks untrusted design data first
if (!parsed.ok) throw new Error(parsed.error); // the message is plain English
const { html, css, script, document, warnings } = compilePageToHtml(
parsed.pages[0],
{ title: 'My page', designSystem, formAction: (id) => `/api/forms/${id}` },
);
// 'document' is a complete page; 'html' + 'css' + 'script' are its parts.
return { page: sanitizeV2Html(document), warnings };
}Or compile with a hosted call
Can't import the SDK on your backend? Send the pages with your API key and get the same result back. Up to 50 pages and 2 MB per request. The response is { html, css, script, document, warnings }(for several pages, those are the first page's, plus a pages list with one entry per page).
const res = await fetch('https://plexo.charisol.io/api/v1/compile-v2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.PLEXO_API_KEY!,
},
body: JSON.stringify({
pages, // or: page (a single page)
designSystem, // optional: from editor.getDesignSystem()
options: { title: 'My page', formActionUrl: 'https://example.com/forms/{formId}' },
}),
});
const { html, css, script, document, warnings } = await res.json();Let AI edit the site
describeDesign gives a model a compact picture of the site, and applyCommands applies what it suggests safely: each change is checked on its own and problems come back in plain words. To show the AI assistant inside the editor, pass onAskSol. Your provider key stays on your server.
import {
SOL_SYSTEM_PROMPT,
applyCommands,
catalogSchema,
describeDesign,
parseSolReply,
type DesignSystem,
type PageDefinition,
} from '@charisol/plexo-sdk/v2';
declare function askMyModel(system: string, user: string): Promise<string>;
export async function askSol(
request: string,
pages: PageDefinition[],
designSystem: DesignSystem,
) {
// Tell the model what the site looks like and what it is allowed to change.
const context = JSON.stringify({
catalog: catalogSchema(),
design: describeDesign(pages, designSystem),
});
const reply = await askMyModel(SOL_SYSTEM_PROMPT, `${context}\n\n${request}`);
const { text, commands } = parseSolReply(reply);
if (!commands) return { text, pages, designSystem };
// Every command is checked on its own; bad ones are reported, good ones apply.
const applied = applyCommands(pages, designSystem, commands);
return { text, pages: applied.pages, designSystem: applied.designSystem, results: applied.results };
}Brand and design system, in sync with Strata
Every page draws its colours, fonts and shapes from one design system, so changing the brand once updates the whole site. Read it with editor.getDesignSystem() and pass it back as designSystem next time. To connect it to Strata, give the editor a strata adapter (helpers are in @charisol/plexo-sdk/v2/strata) and the Branding panel lets people sign in, pick a project, and pull or send changes.
Editor props
| Prop | Type | What it does |
|---|---|---|
| pages | PageDefinition[] | The design to edit. Leave it out to start on a blank page. |
| designSystem | DesignSystem | The site's colours, fonts and shapes. Leave it out for the built-in one. |
| onChange / onDesignSystemChange | function | Called (briefly delayed) after every edit with the latest pages or design system. |
| onSave / autoSave / autoSaveDuration | function / boolean / ms | Adds a Save button and, with autoSave, saves a few seconds after the last edit (3000 ms by default). |
| onPublish | function | Adds a Publish button. What publishing means is up to your product. |
| formAction | (formId) => string | Where each form on the page sends its submissions: your own server. |
| pageSource | PageSource | Your list of pages, and what to do when someone opens, adds, renames or deletes one. |
| features | object | Turn panels off: pages, branding, library, codeView, pageSwitcher, viewSwitch, headerSol. |
| onAskSol | (question, context) => Promise<string> | Turns on the AI assistant. Leave it out and AI stays hidden. |
| onUpload / stockImages / media / loadMedia | functions | Where images go, stock photo search through your server, and your own image library. |
| strata | StrataAdapter | Keeps the design system in step with Strata (see the strata entry point). |
| shell, headerLeft, headerRight | object / node | Fit the editor into your app: a way back, hide your sidebar for room, and your own buttons in the top bar. |
| readOnly, commentMode | boolean | Let reviewers look or comment without changing anything. |
Already using <PlexoBuilder />? Nothing changes for you.
The classic builder (the main export of the same package) for emails and landing pages keeps working exactly as before, with the same API key, the same /api/v1/compile endpoint and the same props. The documentation for it is below, unchanged. New projects should start with SDK 2 above.
Install
npm install @charisol/plexo-sdk react react-dom
Render the component (<PlexoBuilder />)
Pass your Plexo API key to load account settings and AI credit permissions automatically.
import React, { useRef } from 'react';
import { PlexoBuilder, type PlexoBuilderRef } from '@charisol/plexo-sdk';
import '@charisol/plexo-sdk/dist/plexo-sdk.css';
export function TemplateEditor() {
const builderRef = useRef<PlexoBuilderRef>(null);
const handleSave = async () => {
if (builderRef.current) {
// Export raw JSON and compiled HTML for email or landing_page
const { json, html } = await builderRef.current.exportDesign('landing_page');
await fetch('/api/save-template', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ json, html }),
});
}
};
return (
<div style={{ height: '100vh' }}>
<PlexoBuilder
ref={builderRef}
apiKey="pk_live_your_api_key"
mode="landing_page"
useAi={true}
aiTier="AUTO"
themeBgColor={BRAND.primary}
/>
</div>
);
}Next.js App Router (SSR disabled)
Because the visual builder uses drag-and-drop and browser APIs, wrap the import with next/dynamic and set ssr: false.
// components/template-editor.tsx
'use client';
import dynamic from 'next/dynamic';
import type { PlexoBuilderRef } from '@charisol/plexo-sdk';
import '@charisol/plexo-sdk/dist/plexo-sdk.css';
import { BRAND } from "@/lib/theme";
const PlexoBuilder = dynamic(
() => import('@charisol/plexo-sdk').then((m) => ({ default: m.PlexoBuilder })),
{ ssr: false, loading: () => <p>Loading Plexo Visual Editor…</p> }
);
export function TemplateEditorWrapper() {
return <PlexoBuilder apiKey="pk_live_your_key" mode="email" useAi={true} />;
}Component props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'email' | 'landing_page' | Required | Output mode for element rules & compiler HTML |
| apiKey | string | Required | Your Plexo API Key for server-side AI authentication |
| useAi | boolean | false | Enables layout prompt bar & element-level AI prompt popup |
| aiTier | 'AUTO' | 'BASIC' | 'MEDIUM' | 'HIGH' | 'AUTO' | Model complexity request tier; AUTO classifies per prompt |
| themeBgColor | string | BRAND.primary | Primary brand accent color for active elements & selection bounds |
| unsplashKey | string | — | Optional Unsplash key for stock image search modal |