BuilderJS

Add a drag-and-drop editor to your developer platform

BuilderJS is the embeddable, drag-and-drop editor you mount inside your own developer platform — not a SaaS you redirect users to. It is a ~140KB vanilla-JS engine that drops into any DOM node and gives your users a visual surface for building HTML emails and web/landing pages, while your platform keeps ownership of persistence, auth, uploads, and delivery. BuilderJS is the editor only: it produces the markup and a lossless JSON model via getData()/load(); it does not send email, host pages, run automation, or store anything for you. That clean component boundary is exactly why it fits a platform — you embed the editing experience and wire the rest to infrastructure you already run.

Mount it

import { useEffect, useRef } from 'react';
  
  // Load the two BuilderJS assets once (e.g. in your HTML head or layout) —
  // this attaches a GLOBAL window.Builder. There is NO npm package to import:
  //   <script src="/dist/builder.js"></script>
  //   <link rel="stylesheet" href="/dist/builder.css">
  // In Next.js, mount this component with a dynamic import + ssr:false,
  // and add 'use client' at the top — BuilderJS is a client-side DOM mount.
  
  export default function EditorPanel({ themeJson, templates, configData, mediaUrl, onSave }) {
    // No destroy() exists, so guard against React StrictMode's double-mount.
    const mounted = useRef(false);
  
    useEffect(() => {
      if (mounted.current) return; // already mounted (StrictMode re-invoke)
      mounted.current = true;
  
      const builder = new window.Builder({
        mainContainer:     '#MainContainer',     // required — CSS selector string
        widgetsContainer:  '#WidgetsContainer',  // optional
        settingsContainer: '#SettingsContainer', // optional
        saveUrl: '/api/designs/save',            // optional — used by built-in builder.save()
        onBrowse: (handleUrl) => openYourAssetPicker(handleUrl), // your upload/asset picker
      });
  
      // Autosave + dirty-state ride the event bus (NOT an on:{} constructor option).
      builder.events.on(Builder.EVENTS.DOCUMENT_CHANGED, () => {
        // Persist the lossless JSON to YOUR backend; BuilderJS stores nothing.
        onSave?.(builder.getData(), builder.getHtml());
      });
      builder.events.on(Builder.EVENTS.DOCUMENT_SAVED, () => { /* show a "saved" toast */ });
  
      // Restore a saved design (4 positional args, in order). On a new doc, pass your seed JSON.
      builder.load(themeJson, templates, configData, mediaUrl);
    }, [themeJson, templates, configData, mediaUrl, onSave]);
  
    return (
      <div className="builder-shell">
        <div id="WidgetsContainer" />
        <div id="MainContainer" />
        <div id="SettingsContainer" />
      </div>
    );
  }

A component inside your platform, not a service beside it

BuilderJS is built to live inside software you already operate. You load two assets — builder.js and builder.css — which attach a global window.Builder (there is no npm package to import), then construct one instance with new window.Builder({ mainContainer, widgetsContainer, settingsContainer }) pointed at three DOM nodes you control via CSS selector strings. From there your platform owns everything around the editor: routing, accounts, plan gating, and the database your saved designs land in. The library never phones home and never holds your data in a vendor format. It is framework-agnostic vanilla JS, so it mounts the same way whether your dashboard is React, Vue, Svelte, Angular, Next.js, Laravel, or plain HTML — there is no per-framework SDK to adopt and no remote runtime to keep in sync.

You own the data: JSON-first round-trip

Everything BuilderJS produces is yours. builder.getData() returns a serializable JSON document of the entire design, and builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl) restores it on the next boot — feed a saved getData() output back through load() and the design rebuilds exactly. builder.getHtml() emits the rendered markup — standalone page HTML or inline-CSS email HTML — when you are ready to ship it. Your backend decides where that JSON and HTML live; BuilderJS does not run a content API and stores nothing on your behalf. Because the model is portable JSON rather than an opaque blob, you can version it, diff it, and migrate it inside your own schema, which is the contract a platform team actually needs.

Extend and white-label without forking

A platform editor has to look like your product and speak your domain. BuilderJS supports that through registration APIs rather than core patches: add custom Elements, Widgets, and Controls via Builder.registerElement, theme the editor chrome to match your design system, and ship locale packs (20+ locales, with RTL). There is no "powered by" badge to buy out, because you own the source. It is multi-tenant capable — you run one isolated Builder instance per tenant, each with its own event bus and document tree, and the host closure-captures the tenant scope — while your application keeps ownership of tenant isolation, seats, and billing. You extend the surface your users touch without ever forking the library you have to re-merge on every update.

Wire saves and autosave to your own backend

BuilderJS leaves persistence to you, and exposes exactly the seams you need. Pass saveUrl and the built-in builder.save() will POST to your route; or skip it and drive everything yourself. For dirty-state and autosave, subscribe to the editor's event bus: builder.events.on(Builder.EVENTS.DOCUMENT_CHANGED, …) fires (debounced) on every mutation, and DOCUMENT_SAVED fires after a successful save — so you can mark dirty, autosave getData(), or show a "last saved" pill. Asset uploads route through the onBrowse callback: when a user clicks Browse, BuilderJS calls your handler so your own asset picker and upload storage stay in charge. There is no nested save/upload config object and no hosted upload service — just plain hooks into your stack.

Self-host the source, one-time license

BuilderJS is sold as a one-time CodeCanyon source license (item 27146783) — you buy once, own the source, and self-host it on your own infrastructure. There is no monthly fee, no per-seat charge, no usage metering, and no phone-home, which makes cost predictable as your platform scales. It ships reference PHP backends (MySQL/SQLite/S3 for save, upload, and auth) that you copy and own and can port to Node, Go, or Python — they are starting points, not a hosted service. The honest trade-off: author support is time-boxed (roughly 6–12 months), so you are buying source you control rather than an open-ended support contract. You can try every feature live at builder.emotsy.com before buying.

Frequently asked questions

Explore