@charisol/plexo-sdk

中文文档
2.0.26发布于 2026/10/31MITTypeScriptESM + CJS近 30 天下载 2183

Plexo visual email and landing page builder SDK — drag-and-drop template editor with AI integration.

原始使用文档
文档目录30 节
  1. Features
  2. Installation
  3. Quick Start
  4. Plain React (Vite / CRA)
  5. Next.js (App Router)
  6. Step 1 — Create a dynamic wrapper
  7. Step 2 — Use in a Server Component page
  8. Props Reference
  9. PlexoBuilder
  10. AI Configuration
  11. PlexoBuilderRef — Imperative API
  12. V2 editor — @charisol/plexo-sdk/v2
  13. What is a default, what is a prop, what is separate
  14. Theming the editor
  15. Emails
  16. Where a form sends messages
  17. Compiling
  18. The visible canvas
  19. Untrusted input
  20. Upgrading from the previous V2
  21. Framework Notes
  22. Vite
  23. Next.js (Pages Router)
  24. Remix / React Router v7
  25. Changelog
  26. 1.1.0
  27. Unreleased
  28. 1.0.1
  29. 1.0.0
  30. License

@charisol/plexo-sdk

Visual drag-and-drop email & landing page builder SDK with AI integration — built for React.

npm version license


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 — edit a single element, edit the whole layout, or generate a fresh layout from a prompt, with support for OpenAI, Gemini, and Claude
  • 🔒 Server-proxied AI, no exposed keys — your provider API key is never sent to or stored in the browser; every AI request is authenticated with your Plexo API key and resolved against your account's server-side configuration
  • 💬 Claude-Code-style AI feedback — a loading bubble followed by a one-line summary of what changed, for both the per-element editor and the layout prompt bar
  • 🖼️ Stock image search — Unsplash, Pexels, Pixabay integrations
  • 🎨 Design token system — Strata token support for consistent brand theming
  • 📦 ESM-only, pure client-side component — nothing to self-host; the SDK talks directly to Plexo's hosted API for AI/publish/compile using your Plexo API key

Installation

npm install @charisol/plexo-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 '@charisol/plexo-sdk';
// Import the builder's stylesheet
import '@charisol/plexo-sdk/dist/plexo-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 '@charisol/plexo-sdk';
import { useRef } from 'react';

// Import the builder's stylesheet
import '@charisol/plexo-sdk/dist/plexo-sdk.css';

// Never SSR the builder — it relies on browser APIs (drag-and-drop, ResizeObserver, etc.)
const PlexoBuilder = dynamic(
  () => import('@charisol/plexo-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

PropTypeDefaultDescription
mode'email' | 'landing_page'—Required. Output mode for the editor
apiKeystring—Required. Your Plexo API key
initialTemplateTemplateJSON—Pre-load a saved template JSON
initialHtmlstring—Pre-load raw HTML (landing page mode)
initialCssstring—Pre-load CSS (landing page mode)
initialJsstring—Pre-load JS (landing page mode)
showSaveButtonbooleantrueShow/hide the built-in save button
onSave(data: { json, html }) => void—Callback when built-in save button is clicked
autoSavebooleanfalseAuto-save periodically
autoSaveDurationnumber3000Auto-save interval in ms
onUpload(file: File) => Promise<string> | string—Custom image upload handler, returns URL
backgroundColorstring'#0e1526'Canvas background colour
themeBgColorstring'#4f46e5'Primary brand/accent colour
themeFgColorstring'#ffffff'Foreground colour on brand elements
textColorstring'#e2e8f0'Default body text colour
useAibooleanfalseEnable AI content generation. Also gates locally — if false, AI UI is hidden/disabled without a round trip
aiProvider'openai' | 'gemini' | 'claude' | string'openai'Display/label only. The provider actually used is resolved server-side from your Plexo account's configuration — this prop does not select or override it
aiTier'AUTO' | 'BASIC' | 'MEDIUM' | 'HIGH' | string'AUTO'Requested model tier, sent with each AI request as an override of your account's default tier. AUTO lets the server pick BASIC/MEDIUM/HIGH per-request based on prompt complexity
unsplashKeystring—Unsplash API key for stock images
pexelsKeystring—Pexels API key for stock images
pixabayKeystring—Pixabay API key for stock images
readOnlybooleanfalseForces the canvas into read-only preview (no drag-and-drop, no property panels, no Save button) regardless of the built-in Preview/Edit toggle. For host apps enforcing their own permission model (e.g. a viewer/commenter collaborator role). onRequestComment still fires while readOnly is on.
commentModebooleanfalseWhen on, clicking any row/column/element calls onRequestComment with that node's id/type instead of selecting it for editing — the building block for a Figma-style "click to pin a comment" host feature.
onRequestComment(target: CommentTarget) => void—Fired on canvas click while commentMode is on. CommentTarget is { nodeId, nodeType: 'row' | 'column' | 'element', clientX, clientY }.

AI Configuration

AI generation (per-element editing, whole-layout editing, and generating a brand-new layout when the canvas is empty) runs entirely server-side. There is no aiApiKey prop and never will be — a provider API key (OpenAI, Anthropic, or Gemini) is configured once against your Plexo account and stays there, encrypted at rest. The SDK authenticates AI requests with the same apiKey you already pass to PlexoBuilder; the server resolves your account's provider, key, and default tier from that.

<PlexoBuilder
  apiKey="your-plexo-api-key"
  useAi
  aiTier="AUTO" // optional override: 'AUTO' | 'BASIC' | 'MEDIUM' | 'HIGH'
  // no aiApiKey, no aiProvider selection — both are account-level, not props
  ...
/>

Tiers, from cheapest/fastest to most capable: BASIC → MEDIUM → HIGH. AUTO (the default) classifies each prompt's complexity server-side and picks the right tier automatically — a quick "change the button color" won't spend the same budget as "redesign this whole landing page."

Per-template override: TemplateJSON.body.aiConfig can override useAi/tier for a single template:

aiConfig?: {
  useAi?: boolean;
  tier?: string;
}

Notice there's no apiKey/provider field here either — a template's JSON is loaded into the browser wholesale for editing, so a secret stored inside it could never actually be kept safe. The real key always comes from your account's server-side configuration, never from the template.

Where AI requests actually go: if apiKey is "workspace-internal" (the sentinel used when the SDK is rendered by Plexo's own dashboard), requests stay same-origin. For every real Plexo API key — i.e. any third-party integration — requests are routed automatically to Plexo's hosted production API. There's nothing to configure. If a request ever fails with a 404 here, it means this package version is out of date; update with npm install @charisol/plexo-sdk@latest.


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();
MethodSignatureDescription
exportDesign(target: 'email' | 'landing_page') => Promise<{ json: TemplateJSON; html: string }>Compile & export the current design
getJson() => TemplateJSONGet the raw design JSON snapshot
getNodeRect(nodeId: string) => DOMRect | nullOn-screen bounding rect of a row/column/element by its JSON id, or null if not currently rendered. Re-call on scroll/resize/device-view-change. Useful for positioning host UI (e.g. a comment pin) over a specific canvas node.
getCanvasContainerRect() => DOMRect | nullOn-screen bounding rect of the canvas viewport itself.

V2 editor — @charisol/plexo-sdk/v2

A Figma-style visual builder for landing pages and app screens: free, flow or grid layout, per-screen (desktop / tablet / mobile) layouts, and nine ready-made interactive elements (carousel, video, social links, menu, HTML embed, table, countdown timer, form, accordion). A design is plain JSON — an array of pages, each a tree of nodes — and a compiler turns it into HTML or a React component.

import { V2Editor } from '@charisol/plexo-sdk/v2';
import '@charisol/plexo-sdk/dist/plexo-sdk.css';

// Works with no props at all: opens a blank page, fully offline.
<div style={{ height: '100vh' }}>
  <V2Editor pages={savedPages} onChange={setPages} onSave={(pages) => api.save(pages)} />
</div>

The editor fills its container (give the parent a height), and it is client-only like V1 — wrap it in next/dynamic with ssr: false in Next.js.

What is a default, what is a prop, what is separate

Where it livesExamples
SDK default — works with no configurationinside the editorthe whole editing UI, undo (one step per gesture), snapping, desktop/tablet/mobile frames, keyboard shortcuts (scoped to the editor, never the host page), the colour picker, Fit-to-screen, the nine elements, image upload that embeds small images, a blank starting page
Host prop — needs the host's server, accounts or secrets<V2Editor … /> propssaving (onSave, onPublish, autoSave), files (onUpload, onUploadFont, media, stockImages), AI (onAskSol), brand colours (colors), ready-made sections (sections), link targets (linkTargets), where forms send (formAction, apiKey, templateId), permissions (readOnly, commentMode), header slots, turning panels off (features)
Separate entry — no React, runs on a server@charisol/plexo-sdk/v2/compilecompilePageToHtml, compilePageToReact, parsePages, convertLegacyDesign, frame sizes, computePlacements

Streaming SOL. onAskSol(question, context, io) may stream: call io.onText(replySoFar) with the whole reply each time more arrives (the panel shows it growing), and watch io.signal — it aborts when the owner presses Stop. Return the full reply at the end. Hosts that ignore io and just return the reply keep working. Add solAutoApply and each change SOL writes (the fenced plexo-commands list) is made on the page the moment it has arrived, so the page changes while SOL is still answering; one Undo takes back the whole answer. Without solAutoApply the owner still reviews and presses Apply. context also carries nodeName and the panel's earlier history, so follow-ups like "make it shorter" make sense. readStreamingSolReply (exported) is the incremental reader, if a host builds its own panel.

Nothing is faked when a prop is missing: with no onAskSol the SOL button is hidden, with no onSave there is no Save button, with no onUpload images are embedded in the design (up to 1.5 MB) with a clear message beyond that; with no onUploadFont the font picker offers Google Fonts only (no "Add a font" button). Uploaded fonts are stored in the design system (designSystem.fonts) and compiled to @font-face rules; if the host stores font files on another origin, pass it to sanitizeV2Html(html, { fontOrigins: [...] }). compilePageToHtml also returns head (font <link> tags) and fontLinks for hosts that place html + css in their own page. No provider key ever lives in the browser — AI and stock-image search go through the host, as in V1.

Theming the editor

The editor's own colours (not your visitors' page) follow three props. Pass any of them; every other shade (panels, borders, hover states, muted text) is mixed from these, so a light background with dark text works as well as a dark one. Nothing passed = Plexo's lime-on-black look.

<V2Editor theme={{ accent: '#96F02E', background: '#000000', text: '#ffffff' }} />

Emails

The same editor designs emails: <V2Editor mode="email" />. In email mode only what every email app can draw is offered: text, buttons, pictures, dividers, spacing and rows of columns (which stack on a phone), in ready-made email sections (header, main message, text, highlights, call to action, picture, footer). There is no free placement, grid, shadow, animation or script, and Preview shows the email as it will arrive. Write the finished email with the compiler (tables with inline styles, plus a plain-text version):

import { compilePageToEmail, emailIssues, starterEmailPages } from '@charisol/plexo-sdk/v2/compile';
const { html, text, warnings } = compilePageToEmail(page, { designSystem, preheader: 'Your account is ready' });

Merge tags such as {{first_name}} pass through untouched for your sender to fill in.

Where a form sends messages

In order: the form's own address (the owner typed one) → the host's formAction(formId) → inside Plexo (apiKey="workspace-internal" plus templateId) Plexo's own form inbox → nothing, which the editor and the compiler both say plainly. A third-party app with a real Plexo key doesn't get Plexo's inbox (it finds the site from the page's own domain), so it passes formAction.

Compiling

import { compilePageToHtml, compilePageToReact, parsePages } from '@charisol/plexo-sdk/v2/compile';

const parsed = parsePages(json);                       // untrusted JSON in, plain-language error out
if (!parsed.ok) throw new Error(parsed.error);

// Landing pages / templates → HTML
const { html, css, script, document, warnings } = compilePageToHtml(parsed.pages[0], {
  apiKey: 'workspace-internal', templateId,            // inside Plexo: forms work with no setup
  // formAction: (id) => `https://api.acme.test/leads?form=${id}`,   // any other host
  // runtime: 'none',                                   // host forbids scripts on published pages
  // allowEmbedScripts: false,                          // …and wants HTML embeds script-free too
});

// App projects → a standalone React component (imports only "react")
const { code } = compilePageToReact(parsed.pages[0], { componentName: 'HomePage' });

Everything works as plain HTML where the browser allows it (scroll-snap carousel, native accordion and mobile menu, real table, form and video). The optional runtime adds carousel autoplay and arrows, the live timer, and sending a form without leaving the page; hosts that don't allow scripts leave it out with runtime: 'none'. warnings are plain-language things the owner should fix (a form with nowhere to send messages, a screen missing elements the desktop has).

The visible canvas

Each screen (desktop 1200, tablet 768, mobile 375) is a frame. What is inside a frame is the page on that screen; what is outside is working area that is shown, dimmed, only while editing:

  • an element entirely outside a screen's frame is not published on that screen (and is left out of the page altogether if no screen shows it);
  • an element partly outside is clipped at its section's edge;
  • the editor counts what's outside, and offers Fit to this screen (mobile: one stacked column; tablet: the layout scaled to width) and Bring into view. The compiler warns when a screen is missing elements the desktop has.

Since a section clips at its edge, leave room under things that grow (an accordion, a form with an error message).

Untrusted input

A design can come from a database, an AI or a third-party app, so the compilers treat it as untrusted: text and attributes are escaped, link and image schemes are allow-listed, CSS values that could add declarations or fetch URLs are dropped, class names are restricted, embeds run in a sandbox, and parsePages rejects unknown element types, duplicate ids and oversized trees. Don't pass V2 HTML through V1's sanitizeCompiledHtml. It keeps V2's stylesheet and layout, but removes <details>, <iframe>, <figure> and <script> — which silently breaks the accordion, the phone menu, video, HTML embeds and the carousel. V2's output is built from an allow-list of tags and attributes, so it doesn't need that step; a host that must sanitize should use a sanitizer that allows those tags.

Upgrading from the previous V2

The previous V2 (V2Canvas, useCanvasStore, compilePageToReact(nodes, pageOrder, …)) is replaced; this is a breaking change to @charisol/plexo-sdk/v2 and /v2/compile. Designs saved by the old version still open: isLegacyDesign(doc) recognises them and convertLegacyDesign(doc, { name, slug }) upgrades one to a page (the old pages scaled to fit any screen, so tablet and mobile get matching scaled layouts). Not carried over: brand text-style links and pinned sections.


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('@charisol/plexo-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('@charisol/plexo-sdk').then((m) => ({ default: m.PlexoBuilder }))
);

// In your component:
<Suspense fallback={<p>Loading…</p>}>
  <PlexoBuilder ... />
</Suspense>

Note: In Remix, place this inside a clientLoader or use <ClientOnly> wrapper to ensure it's never executed on the server.


Changelog

1.1.0

  • New readOnly, commentMode, and onRequestComment props, plus getNodeRect/getCanvasContainerRect on the PlexoBuilderRef — lets a host build Figma-style anchored comments on top of the canvas (click a node while commentMode is on to get its id/type/coordinates, position an overlay via getNodeRect, enforce a read-only collaborator role via readOnly) without needing to know the canvas's internal DOM structure. Fully backwards compatible — no existing prop or ref method changed.
  • Every row/column/element wrapper now carries a data-plexo-node-type="row"|"column"|"element" attribute alongside its existing id (already the node's stable JSON id).

Unreleased

  • Breaking: Removed the aiApiKey prop. AI generation now runs server-side — your provider key is configured once against your Plexo account (encrypted at rest) instead of being passed to the browser on every render.
  • Breaking: TemplateJSON.body.aiConfig no longer accepts apiKey/provider — only useAi/tier remain, for the same reason (a template's JSON is loaded client-side, so it can't hold a secret safely).
  • aiProvider is now display/label-only; the effective provider comes from your account configuration, not this prop.
  • New Claude-Code-style AI feedback: a loading bubble followed by a one-line summary of what changed, for both the per-element "Edit with AI" popup and the layout AI prompt bar.
  • The layout AI prompt bar now generates a brand-new layout automatically when the canvas is empty, instead of only editing existing content.
  • AI requests (and publish/validate-key requests, when using a real Plexo API key rather than the dashboard's internal session) now route automatically to Plexo's hosted production API — nothing to configure. A 404 here means the installed package version is stale; update to the latest.

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

版本与发布记录

@charisol/plexo-sdk 共发布 134 个版本,首次发布于 2026/07/22;过去 12 个月发布了 134 个版本。

版本发布日期距今
2.0.26latest2026/10/3110 天前
2.0.252026/10/2615 天前
2.0.242026/10/2119 天前
2.0.232026/10/2119 天前
2.0.222026/10/2020 天前
2.0.212026/10/2020 天前

其他发布通道:next → 1.4.1。

关于 @charisol/plexo-sdk 的常见问题

@charisol/plexo-sdk 是什么?

Plexo visual email and landing page builder SDK — drag-and-drop template editor with AI integration.

如何安装 @charisol/plexo-sdk?

在项目目录运行 npm install @charisol/plexo-sdk,或使用 pnpm add / yarn add / bun add。

@charisol/plexo-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@charisol/plexo-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@charisol/plexo-sdk 可以商用吗?

@charisol/plexo-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@charisol/plexo-sdk 的下载量有多少?

根据 npm 官方下载统计,@charisol/plexo-sdk 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,183 次,日均约 73 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@charisol/plexo-sdk 还在维护吗?

最新版本 2.0.26 发布于 2026/10/31(10 天前);至今共发布 134 个版本,其中过去 12 个月发布了 134 个;首次发布于 2026/07/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。