@plexobuilder/sdk
Visual drag-and-drop email & landing page builder SDK with AI integration — built for React.
Features
- Visual drag-and-drop builder — rows, columns, and elements with live editing
- Email & Landing Page modes — export clean, production-ready HTML for both
- AI content generation — built-in support for OpenAI, Gemini, and Claude
- Stock image search — Unsplash, Pexels, Pixabay integrations
- Design token system — Strata token support for consistent brand theming
- ESM-only, pure client-side — zero server-side dependencies
Installation
npm install @plexobuilder/sdk
Peer dependencies (install if not already present):
npm install react react-dom
Quick Start
Plain React (Vite / CRA)
import { useRef } from 'react';
import { PlexoBuilder, type PlexoBuilderRef } from '@plexobuilder/sdk';
// Import the builder's stylesheet
import '@plexobuilder/sdk/dist/sdk.css';
export function Editor() {
const builderRef = useRef<PlexoBuilderRef>(null);
const handleSave = async () => {
const result = await builderRef.current?.exportDesign('email');
console.log('HTML:', result?.html);
console.log('JSON:', result?.json);
};
return (
<div style={{ height: '100vh' }}>
<PlexoBuilder
ref={builderRef}
apiKey="your-api-key"
mode="email"
backgroundColor="#ffffff"
themeBgColor="#6d28d9"
themeFgColor="#ffffff"
textColor="#111827"
showSaveButton={false}
/>
<button onClick={handleSave}>Save</button>
</div>
);
}
Next.js (App Router)
Important: The SDK is a purely client-side component that uses drag-and-drop and browser APIs. It must never be rendered on the server.
Step 1 — Create a dynamic wrapper
Because the SDK uses browser-only APIs, wrap it with next/dynamic and disable SSR. Be sure to import the CSS stylesheet:
// components/template-editor.tsx
'use client';
import dynamic from 'next/dynamic';
import type { PlexoBuilderRef } from '@plexobuilder/sdk';
import { useRef } from 'react';
// Import the builder's stylesheet
import '@plexobuilder/sdk/dist/sdk.css';
// Never SSR the builder — it relies on browser APIs (drag-and-drop, ResizeObserver, etc.)
const PlexoBuilder = dynamic(
() => import('@plexobuilder/sdk').then((m) => ({ default: m.PlexoBuilder })),
{ ssr: false, loading: () => <p>Loading builder…</p> }
);
export function TemplateEditor() {
const builderRef = useRef<PlexoBuilderRef>(null);
const handleSave = async () => {
const result = await builderRef.current?.exportDesign('email');
console.log(result?.html);
};
return (
<div style={{ height: '100vh' }}>
<PlexoBuilder
ref={builderRef}
apiKey="your-api-key"
mode="email"
backgroundColor="#ffffff"
themeBgColor="#6d28d9"
themeFgColor="#ffffff"
textColor="#111827"
showSaveButton={false}
/>
<button onClick={handleSave}>Save</button>
</div>
);
}
Step 2 — Use in a Server Component page
// app/editor/page.tsx (Server Component — no "use client")
import { TemplateEditor } from '@/components/template-editor';
export default function EditorPage() {
return <TemplateEditor />;
}
Props Reference
PlexoBuilder
| Prop | Type | Default | Description |
|---|---|---|---|
mode |
'email' | 'landing_page' |
— | Required. Output mode for the editor |
apiKey |
string |
— | Required. Your Plexo API key |
initialTemplate |
TemplateJSON |
— | Pre-load a saved template JSON |
initialHtml |
string |
— | Pre-load raw HTML (landing page mode) |
initialCss |
string |
— | Pre-load CSS (landing page mode) |
initialJs |
string |
— | Pre-load JS (landing page mode) |
showSaveButton |
boolean |
true |
Show/hide the built-in save button |
onSave |
(data: { json, html }) => void |
— | Callback when built-in save button is clicked |
autoSave |
boolean |
false |
Auto-save periodically |
autoSaveDuration |
number |
3000 |
Auto-save interval in ms |
onUpload |
(file: File) => Promise<string> | string |
— | Custom image upload handler, returns URL |
backgroundColor |
string |
'#ffffff' |
Canvas background colour |
themeBgColor |
string |
'#000000' |
Primary brand/accent colour |
themeFgColor |
string |
'#ffffff' |
Foreground colour on brand elements |
textColor |
string |
'#000000' |
Default body text colour |
useAi |
boolean |
false |
Enable AI content generation |
aiProvider |
'openai' | 'gemini' | 'claude' |
'openai' |
AI provider |
aiApiKey |
string |
— | API key for the AI provider (client-side) |
aiTier |
'AUTO' | 'BASIC' | 'MEDIUM' | 'HIGH' |
'AUTO' |
AI model tier |
unsplashKey |
string |
— | Unsplash API key for stock images |
pexelsKey |
string |
— | Pexels API key for stock images |
pixabayKey |
string |
— | Pixabay API key for stock images |
PlexoBuilderRef — Imperative API
Access via ref on the component:
const builderRef = useRef<PlexoBuilderRef>(null);
// Export the current design
const { json, html } = await builderRef.current.exportDesign('email');
// or:
const { json, html } = await builderRef.current.exportDesign('landing_page');
// Get the JSON without compiling HTML
const json = builderRef.current.getJson();
| Method | Signature | Description |
|---|---|---|
exportDesign |
(target: 'email' | 'landing_page') => Promise<{ json: TemplateJSON; html: string }> |
Compile & export the current design |
getJson |
() => TemplateJSON |
Get the raw design JSON snapshot |
Compiler Utilities (Server-safe)
These utilities run without browser APIs — safe to use in Node.js, server components, or API routes:
import {
compileToHTML,
compileToPlainText,
compileToHTMLWithStrataAuto,
parseJsonToTargetFormat,
injectStrataBundle,
} from '@plexobuilder/sdk';
| Utility | Description |
|---|---|
compileToHTML(json, target) |
Compile TemplateJSON to HTML string |
compileToPlainText(json) |
Extract plain text from a template |
compileToHTMLWithStrataAuto(json, target, tokens?) |
Compile with Strata design tokens inlined |
parseJsonToTargetFormat(json, target) |
Parse and validate a template JSON |
injectStrataBundle(html, tokens) |
Inject Strata runtime CSS into compiled HTML |
Example — Compile on the server (Next.js API route)
// app/api/compile/route.ts
import { compileToHTML } from '@plexobuilder/sdk';
import type { TemplateJSON } from '@plexobuilder/sdk';
export async function POST(req: Request) {
const { template }: { template: TemplateJSON } = await req.json();
const html = compileToHTML(template, 'email');
return Response.json({ html });
}
Design Token Utilities
import {
createTokenMapping,
applyTokensToElement,
getTokensByCategory,
generateTokenCSS,
} from '@plexobuilder/sdk';
TypeScript Types
import type {
TemplateJSON,
RowJSON,
ColumnJSON,
ElementJSON,
ElementType,
PlexoBuilderProps,
PlexoBuilderRef,
PlexoTargetType,
StrataToken,
StrataTokenSet,
CompileTarget,
} from '@plexobuilder/sdk';
Framework Notes
Vite
Works out of the box — no extra config needed.
Next.js (Pages Router)
Same dynamic import pattern applies:
import dynamic from 'next/dynamic';
const PlexoBuilder = dynamic(
() => import('@plexobuilder/sdk').then((m) => ({ default: m.PlexoBuilder })),
{ ssr: false }
);
Remix / React Router v7
Use the lazy approach or a client-only guard:
import { lazy, Suspense } from 'react';
const PlexoBuilder = lazy(() =>
import('@plexobuilder/sdk').then((m) => ({ default: m.PlexoBuilder }))
);
// In your component:
<Suspense fallback={<p>Loading…</p>}>
<PlexoBuilder ... />
</Suspense>
Note: In Remix, place this inside a
clientLoaderor use<ClientOnly>wrapper to ensure it's never executed on the server.
Changelog
1.0.1
- Fixed: Removed Rolldown CJS interop shim (
require()) from ESM output — resolves compatibility issues with Turbopack (Next.js 15+/16+) and strict ESM environments - All React sub-paths (
react/jsx-runtime, etc.) are now properly externalized
1.0.0
- Initial release
License
MIT Plexo HQ