@pagehub/sdk
v0.1.8
Published
Embeddable visual page builder SDK — drop a full drag-and-drop page editor into any web app, or render published sites anywhere JS runs.
Maintainers
Readme
@pagehub/sdk
Drop a full visual page builder into any web app.
A drag-and-drop page editor SDK built on CraftJS. Includes a visual canvas, component toolbox, settings panels, responsive preview, multi-page support, AI generation, SEO controls, and static HTML export — all configured through a single config object.
Install
@pagehub/sdk ships three sub-path entry points so you only pay for what you use:
| Use case | Import | Extras to install |
|---|---|---|
| Editor (drag-and-drop builder) | @pagehub/sdk | the editor-extras snippet below |
| React viewer (render a published site in a React app) | @pagehub/sdk/react | none (just React) |
| Static HTML (render to an HTML string in Workers / Deno / Bun / Node) | @pagehub/sdk/html | none |
Viewer / static-only install
npm install @pagehub/sdk react react-domEditor install (adds chrome deps as optional peers)
npm install @pagehub/sdk react react-dom \
@tiptap/core @tiptap/pm @tiptap/react @tiptap/starter-kit \
@tiptap/extension-bold @tiptap/extension-code @tiptap/extension-color \
@tiptap/extension-document @tiptap/extension-font-family \
@tiptap/[email protected] @tiptap/extension-highlight \
@tiptap/extension-image @tiptap/extension-italic @tiptap/extension-link \
@tiptap/extension-placeholder @tiptap/extension-strike \
@tiptap/extension-subscript @tiptap/extension-superscript \
@tiptap/extension-text-align @tiptap/extension-text-style \
@tiptap/extension-underline \
@codemirror/autocomplete @codemirror/lang-css @codemirror/lang-html \
@codemirror/lang-javascript @codemirror/language @codemirror/lint \
@codemirror/view @uiw/react-codemirror @lezer/highlight \
@tailwindcss/browser @floating-ui/react-dom @headlessui/react \
@hello-pangea/color-picker use-eye-dropper react-window swr \
gsap framer-motionComponent-level optional peers
Only install if your site actually uses these components:
# Map component
npm install leaflet react-leaflet
# Video component (YouTube provider)
npm install react-youtubeDeveloping inside the monorepo: see CONTRIBUTING.md for setup instructions.
Quick Start
React / Next.js
import { PageHubEditor } from "@pagehub/sdk";
import "@pagehub/sdk/editor.css";
export default function Builder() {
return (
<PageHubEditor
callbacks={{
onLoad: async () => {
const res = await fetch("/api/pages/home");
return res.ok ? res.json() : null; // null = blank canvas
},
onSave: async pageData => {
await fetch("/api/pages/home", {
method: "PUT",
body: JSON.stringify(pageData),
});
},
}}
theme={{ primaryColor: "#2563eb" }}
/>
);
}SSR note: The editor uses browser APIs. In Next.js, load it with
next/dynamic:const Builder = dynamic(() => import("./Builder"), { ssr: false });
Vanilla JS (any framework)
<div id="editor" style="height: 100vh"></div>
<script type="module">
import PageHub from "@pagehub/sdk";
import "@pagehub/sdk/editor.css";
const editor = PageHub.init({
container: "#editor",
callbacks: {
onLoad: async () => {
const res = await fetch("/api/pages/home");
return res.ok ? res.json() : null; // null = blank canvas
},
onSave: async pageData => {
await fetch("/api/pages/home", {
method: "PUT",
body: JSON.stringify(pageData),
});
},
},
});
</script>Configuration
resolveConfig(config)
| Option | Type | Default | Description |
| ------------ | ----------------------- | ---------- | ------------------------------------------------------- |
| container | string \| HTMLElement | — | DOM selector or element to mount into (vanilla JS only) |
| apiKey | string | — | PageHub Cloud API key (optional for self-hosted) |
| apiBaseUrl | string | "" | API base URL (for AI, uploads, forms) |
| pageId | string | — | Initial page ID to load |
| readOnly | boolean | false | Start in viewer mode |
| callbacks | PageHubCallbacks | required | Your integration hooks (see below) |
| theme | PageHubTheme | — | Visual theming |
| features | PageHubFeatures | — | Feature toggles |
| ai | PageHubAIConfig | — | AI generation config |
| locale | PageHubLocale | — | Localization overrides |
Callbacks
The two required callbacks are how you connect PageHub to your backend:
callbacks: {
onLoad: (pageId?) => Promise<PageData | null>, // fetch page — return null for blank canvas
onSave: (pageData, meta?) => Promise<void>, // persist page
// Optional
onChange: (pageData) => void, // fires on every edit (debounced) — useful for autosave
onPublish: (pageData) => Promise<void>, // user clicked "Publish"
onMediaUpload: (file) => Promise<string>,// upload a file, return the public URL
onMediaDelete: (url) => Promise<void>, // delete a previously uploaded file
}The pageData object you receive on save:
{
content: string; // compressed editor state — store this to reload later
html?: string; // rendered static HTML — ready to publish
classes?: string[]; // Tailwind classes used — for CSS purging/compilation
title?: string;
seo?: PageSeo;
}Theme
theme: {
primaryColor: "#2563eb",
secondaryColor: "#7c3aed",
accentColor: "#06b6d4",
colorScheme: "system", // "light" | "dark" | "system"
logo: "/your-logo.svg", // shown in editor toolbar
cssVariables: { ... }, // custom CSS vars (no -- prefix needed)
customCSS: "...", // raw CSS injected into the editor
}Adopting host design tokens (shadcn / Tailwind v4): if your app already defines a shadcn-style palette under --color-* vars, add one import to rebind PageHub's DaisyUI tokens to the host vars:
import "@pagehub/sdk/editor.css";
import "@pagehub/sdk/themes/shadcn.css"; // load AFTER editor.cssMaps --color-background → --base-100, --color-foreground → --base-content, --color-primary → --primary, --radius → --radius-box/-field/-selector, etc. Each binding falls back to the DaisyUI default if the host hasn't defined the matching shadcn var. See docs/sdk/theme.md for the full mapping table.
Features
Toggle editor capabilities on or off:
features: {
sidebar: true, // component panel
toolbar: true, // top toolbar
saveButton: true, // save/publish button in top toolbar
aiGeneration: false, // AI content generation (requires ai config)
multiPage: true, // multi-page site editing
responsivePreview: true, // device preview toggle
seoPanel: true, // SEO settings
importExport: true, // Import/Export row in More menu
settingsPanelSwitcher: true, // "Left/Right Settings Panel" row in More menu
darkModeSwitcher: true, // "Switch to Dark/Light Theme" row in More menu
customCSS: false, // CSS editor panel
restrictedComponents: [], // component names to hide from toolbox
}AI
ai: {
enabled: true, // requires a PageHub account
}AI features (content generation, assistant) are powered by PageHub's API. Users can configure their own AI provider keys in their PageHub account settings.
Instance API
PageHub.init() returns an instance with these methods:
const editor = PageHub.init({ ... });
// Save & load
editor.save({ isDraft: true });
editor.load("page-id");
editor.getPageData(); // { content, html, classes, ... }
// HTML export
editor.getHTML(); // rendered HTML string
editor.exportHTML({ // full static HTML + metadata
document: true, // wrap in <html><head>...</head><body>
title: "My Page",
includeThemeVars: true,
});
// JSON import/export
editor.exportJSON(); // raw CraftJS JSON string
editor.importJSON(jsonString); // load from JSON
// Runtime updates
editor.setReadOnly(true); // toggle viewer mode
editor.setTheme({ primaryColor: "#e11d48" });
editor.setFeatures({ sidebar: false });
// Events
const off = editor.on("save", (data) => console.log("Saved!", data));
off(); // unsubscribe
// Cleanup
editor.destroy();Events
| Event | Payload | When |
| ------------------- | ---------------------- | -------------------------------- |
| ready | — | Editor mounted and ready |
| save | PageData | User or programmatic save |
| load | PageData | Page loaded |
| change | PageData | Editor state changed (debounced) |
| publish | PageData | User published |
| error | Error | Something went wrong |
| modeChange | "editor" \| "viewer" | Read-only toggled |
| componentSelect | node info | User selected a component |
| componentDeselect | — | Selection cleared |
Three runtimes — import only what you need
The SDK ships as three independent entry points. Pages built in the full editor render identically through all three:
| Entry | Use for | Ships | Doesn't ship |
| ------------------------------ | ------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------- |
| @pagehub/sdk | The visual editor | Full editor chrome, toolbox, inspector, TipTap, CodeMirror, GSAP | — |
| @pagehub/sdk/viewer | Live published pages (React) | React render runtime, theme + animation presets, action handlers | Editor chrome, TipTap, CodeMirror, GSAP editor harness |
| @pagehub/sdk/static-renderer | SSG / Node / edge / email HTML / build scripts | Pure string-based HTML walker, theme CSS, class collector | React, ReactDOM, any browser API |
Use the editor on /edit/[id], the viewer on /preview/[id], and the static renderer in getStaticProps, an edge function, or a Node export script. Mix and match — same content payload everywhere.
Viewer — read-only React mode
Render saved pages with interactivity (forms, modals, scroll animations, conditional visibility) but without the editor:
import { PageHubViewer } from "@pagehub/sdk/viewer";
<PageHubViewer content={savedContent} resolver={resolver} />;- No editor chrome. Toolbox, inspector, settings panels, and the
pagehub-sdk-rootshell are all gone — just the page. - No heavy editor deps. TipTap, CodeMirror, GSAP editor harness, and the prop-system UI are tree-shaken out.
- Tailwind injected at runtime. No stylesheet import needed; pass your own theme CSS via the host page if you want SSR-correct styling.
- Same
contentpayload. WhateveronSavegave you is what the viewer reads — no transform step.
Good fit for: live published sites, preview routes, in-app page rendering, A/B test variants.
Static HTML Renderer — no React, no browser
Render pages to a plain HTML string from anywhere a function can run — Node scripts, edge workers, getStaticProps, email-template pipelines, search-engine snapshots:
import { renderToHTML } from "@pagehub/sdk/static-renderer";
// From compressed content (what onSave gives you)
const { html, classes, fontUrls } = renderToHTML(savedContent);
// From raw JSON
const { html } = renderToHTML(jsonString, { compressed: false });
// Full standalone document
const { html } = renderToHTML(savedContent, {
document: true,
title: "My Page",
});- Zero React dependency. Walks the node tree and emits strings directly — runs in Cloudflare Workers, Deno, Bun, and Node without polyfills.
- Returns everything you need to ship.
htmlfor the body,classesfor Tailwind compilation/purging,fontUrlsfor<link>tags. - Identical output to the viewer. Same theme, same component registry, same action wiring — interactive scripts are inlined into the HTML where needed.
document: truewraps the output in a full<html><head>...</head><body>document, ready to write to disk or pipe into an email.
Good fit for: SSG (Next.js / Astro / 11ty), next export, on-publish HTML uploads to a CDN, email rendering, search-engine prerender, static site exports for hosting elsewhere.
Note on
Container.overflow.*— when a container usesoverflow.dragScroll,overflow.autoHide,overflow.wheelHorizontal,overflow.smoothing, oroverflow.hideDelay, both the viewer andrenderToHTMLaddoverflow-x-autotoclassName(unless anotheroverflow-x-*utility is already set) and inline a small script that enables pointer-drag and wheel-to-horizontal scrolling. These are CSS overflow UX options, not the GSAPscrollEffect(horizontal-scroll/scroll-timeline).
Built-in Components
The SDK ships with these drag-and-drop components out of the box:
| Component | Description |
| ---------------- | ----------------------------------------- |
| Container | Flex/grid layout wrapper |
| Header | Page header with nav |
| Footer | Page footer |
| Text | Rich text with inline editing |
| Image | Responsive images |
| Button | CTA button with variants |
| Video | Video embed (YouTube, Vimeo, self-hosted) |
| Audio | Audio player |
| Embed | Raw HTML / iframe embed |
| Form | Form container |
| FormElement | Input, select, textarea, etc. |
| Background | Full-bleed background section |
Custom Components
Register your own draggable component with defineComponent. One file, one function call, no SDK fork — the same components array is read by the editor, viewer, and static renderer.
import { PageHubEditor, defineComponent } from "@pagehub/sdk";
const PricingCard = ({ title, price, period }) => (
<div className="rounded-box border p-6">
<h3>{title}</h3>
<p>${price}/{period}</p>
</div>
);
const PricingCardDef = defineComponent({
name: "PricingCard",
category: "Marketing",
component: PricingCard,
defaultProps: { title: "Pro", price: 29, period: "mo" },
props: {
title: { type: "text", label: "Plan Name" },
price: { type: "number", label: "Price", min: 0 },
period: { type: "select", label: "Period", options: [
{ label: "Month", value: "mo" },
{ label: "Year", value: "yr" },
]},
},
toHTML: ({ props }) =>
`<div class="rounded-box border p-6"><h3>${props.title}</h3><p>$${props.price}/${props.period}</p></div>`,
});
<PageHubEditor
components={[PricingCardDef]}
callbacks={{ onLoad, onSave }}
/>;Vanilla JS is the same — pass components: [PricingCardDef] into PageHub.init().
Working starters: PageHubApp/demos — runnable vanilla-html, vite-react, and nextjs integrations (the vite-react / nextjs ones register a custom defineComponent).
Full reference: docs/sdk/registration-host.md — every field on defineComponent, custom inspector UIs, toHTML rules, viewer/static parity, common gotchas.
If you're modifying the SDK itself to add a new built-in (alongside Container / Text / Button), the surface is different — 8 registrations inside
packages/sdk. Seedocs/sdk/registration.md. Host-app custom components do not need that checklist.
CSS
Import the editor stylesheet in your app:
import "@pagehub/sdk/editor.css";Vite bundles src/css/editor.css into dist/editor.css (same public path: @pagehub/sdk/editor.css). Source is split under src/css/editor-partials/*.css for maintainability; the entry file only wires Tailwind (@import 'tailwindcss'), @reference / @source, and an @import chain.
Scoping: Almost all editor chrome rules are prefixed with .pagehub-sdk-root so generic selectors (button, .input, #viewport, [data-enabled], scrollbars, etc.) do not hit the host page when the builder is embedded. ph-anim-* / ph-hover-* / css-* keyframes stay global so saved page content and static export still work. Mobile preview toggles mobile-preview on .pagehub-sdk-root (not <html>). Portaled listboxes set pagehub-sdk-root ph-select-content on the panel so dropdown styles apply after teleport.
Theming (variables first)
- Prefer theme tokens from your host: load shared design CSS (for PageHub apps this is your design system CSS) so
--base-100,--primary,--border, etc. match the editor chrome. config.theme.cssVariablesandconfig.theme.customCSSstill apply on top for integration-specific overrides.@tailwindcss/browser(used for in-canvas utilities) compiles from live class attributes;@sourceineditor.cssscans SDK sources (../).
Editor CSS partials (concern → file)
Partials that use @apply / @utility rely on css/editor-partials/tailwind-theme-reference.css, imported immediately after tailwindcss in editor.css. @reference URLs are resolved from packages/sdk/src/css/, not from the partial file path. Add new @import lines only at the top of editor.css (PostCSS requires every @import before @source / other at-rules).
| Concern | File |
| ----------------------------------------------------------------- | -------------------------------------------------- |
| Tailwind theme @reference (single, paths from src/css/) | css/editor-partials/tailwind-theme-reference.css |
| Scoped .btn / .input-hover (not @utility) | css/editor-partials/utilities.css |
| Google icons + mobile preview overrides | css/editor-partials/icons-and-mobile-preview.css |
| Canvas selection, drag/drop, #viewport handles | css/editor-partials/canvas-interaction.css |
| Shell typography on .pagehub-sdk-root, scrollbars, range thumbs | css/editor-partials/base-and-scrollbars.css |
| #viewport / [data-renderer] containment | css/editor-partials/viewport-layout.css |
| Toolbar inputs, buttons, sliders | css/editor-partials/toolbar-forms.css |
| Third-party (e.g. Sketch color picker) | css/editor-partials/third-party.css |
| HeadlessUI listbox (ph-select-*) | css/editor-partials/dropdowns.css |
| Scroll/hover presets + css-* keyframes | css/styles.css (animation presets section) |
| Sequential spotlight presets (chain/grid) | css/styles.css (spotlight presets section) |
Naming contract (integrators & contributors)
pagehub-*— Editor shell surface (e.g.pagehub-sdk-root). Prefer this prefix for new chrome-only classes.ph-*— Supported shorthand today for dropdowns and motion utilities:ph-select-*,ph-anim-scroll,ph-hover-*, plusph-in-viewfor scroll-triggered play state. External CSS that targets these may break if we rename them; a futurepagehub-*migration would be one coordinated release with a changelog note.data-*on canvas nodes — Treat as stable DOM protocol (persisted / editor behaviour). Do not rename without a data migration story.
Dual CSS in this repo
@pagehub/sdk/editor.css(SDK dist) — Full editor + Tailwind scan + chrome. Use when embedding the SDK.styles/editor.css(Next app) — App-specific globals that overlap conceptually but are not the same artifact; keep SDK changes inpackages/sdk/src/css/editor.cssandpackages/sdk/src/css/editor-partials/.
PageHub Cloud Features
Some editor features require a PageHub account or self-hosted backend:
| Feature | Requires Backend | Notes |
| ---------------------------- | ---------------- | ------------------------------------------------------------- |
| Drag-and-drop editor | No | Works standalone |
| Viewer / static renderer | No | Works standalone |
| AI content generation | Yes | Enable with ai: { enabled: true }, requires PageHub account |
| Image uploads | Yes | Provide onMediaUpload callback or use PageHub CDN |
| Domain settings | Yes | Requires PageHub Cloud |
| Multi-tenant / user profiles | Yes | Requires PageHub Cloud |
The core editor and viewer work fully offline with just the onLoad/onSave callbacks.
Contributing
Contributions are welcome! See CONTRIBUTING.md for dev setup, architecture, and PR guidelines.
