BuilderJS

Embed a Vue 3 Drag and Drop Page Builder Component

BuilderJS is a framework-agnostic, vanilla-JavaScript drag-and-drop editor for HTML email and pages that you embed directly inside your own Vue 3 application. There is no Vue wrapper to install and nothing to wire into your component tree beyond three container divs and a mount hook. You load the engine as a global browser script, render the canvas, widgets, and settings nodes in your `<template>`, then instantiate `window.Builder` inside `onMounted`. The editor takes over those DOM nodes client-side. BuilderJS is the editor you embed — it does not send email, host pages, or publish anything; you keep your own backend for sending and hosting. You own the source, self-host the assets, and pay a one-time CodeCanyon license (Regular $52 / Extended $99) with no SaaS, monthly fees, or per-seat pricing. This page shows the exact, copy-paste Vue 3 mount pattern that works against the real BuilderJS API.

Mount it

<script setup>
import { onMounted } from 'vue'

let builder = null

onMounted(() => {
  if (builder) return // instantiate exactly once
  builder = new window.Builder({
    mainContainer: '#bjs-canvas',
    widgetsContainer: '#bjs-widgets',
    settingsContainer: '#bjs-settings',
  })
  builder.load(window.THEME_JSON, window.THEME_TEMPLATES, window.THEME_CONFIG_DATA, window.MEDIA_URL)
  // Persist later: const json = builder.getData(); POST json to your backend.
})
</script>

<template>
  <div id="bjs-widgets"></div>
  <div id="bjs-canvas"></div>
  <div id="bjs-settings"></div>
</template>

Load the global script, not an npm import

BuilderJS ships as a compiled browser bundle, not an ES module. Add the stylesheet and script to your page once — for example in index.html or your app shell: <link rel="stylesheet" href="/builderjs/builder.css"> and <script src="/builderjs/builder.js"></script>. Loading that script attaches a single global, window.Builder. There is no module export, so do NOT write import { Builder } from 'builderjs' — that will fail because no such package or named export exists. In your Vue component you reference window.Builder directly. Because the bundle is self-hosted, the path is whatever folder you serve the dist files from; the demo at builder.emotsy.com serves them under /builderjs/. This keeps the editor decoupled from your bundler and lets the same vanilla engine run identically in Vue, React, or a plain PHP page.

Render three containers and mount client-side in onMounted

BuilderJS is a DOM-mount bundle: it attaches to existing elements and never runs during server-side rendering. Render exactly three container nodes in your component template — <div id="bjs-widgets">, <div id="bjs-canvas">, and <div id="bjs-settings"> — then instantiate inside Vue's onMounted lifecycle hook so the DOM exists before the engine queries it. The constructor takes CSS selector strings, not refs or DOM nodes: new Builder({ mainContainer: '#bjs-canvas', widgetsContainer: '#bjs-widgets', settingsContainer: '#bjs-settings' }). Only mainContainer is required; widgetsContainer and settingsContainer are optional and the matching sub-panel simply does not mount if you omit the selector. Internally each option is passed to document.querySelector, which is why the values must be selector strings. If you use Nuxt or any SSR setup, gate the mount to the client (onMounted only runs client-side) so the engine never executes on the server.

Persist with getData() and restore with the 4-argument load()

To save the current design, call const json = builder.getData() and POST that object to your own backend route. getData() returns the full serializable snapshot of the editor state. To restore or seed content, call builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl) with exactly four positional arguments, in that order: the theme/page tree JSON, the theme templates map, the theme config data (fonts, colors, sizes), and the base media URL for relative asset paths. That same call seeds a fresh editor on first paint and rehydrates a previously saved design — pass the snapshot you stored from getData() back as the first argument. Persistence and transport are entirely yours: BuilderJS hands you the JSON and consumes the JSON, and you decide where it lives. There is no built-in autosave or server sync.

No destroy() — instantiate exactly once

BuilderJS has no destroy(), teardown, or dispose method, so do not call builder.destroy() in onBeforeUnmount — there is nothing to dispose. When Vue unmounts your component it removes the container DOM nodes, and the editor goes with them; the garbage collector reclaims the instance once nothing references it. The only thing you need to guard against is creating two instances on the same containers. In ordinary Vue, onMounted runs once per mount so a plain instantiation is fine. If you ever mount the component twice against the same nodes (hot-reload, or a strict dev double-invoke pattern), keep a flag so you call new Builder(...) exactly once. Treat the instance as single-use per mounted component: build it in onMounted, use it while the component lives, and let unmount discard the DOM.

Frequently asked questions

Explore