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
-
Is there an official Vue component or npm package for BuilderJS?
No. BuilderJS is a framework-agnostic vanilla-JS bundle that attaches window.Builder when its script loads. You embed it in Vue by rendering three container divs and calling new Builder(...) inside onMounted. There is no npm package and no import { Builder } statement — load the global script instead.
-
Why must I pass selector strings instead of Vue refs?
The constructor options mainContainer, widgetsContainer, and settingsContainer are passed straight to document.querySelector, so they must be CSS selector strings like '#bjs-canvas'. Passing a ref or DOM node will not work. Give your containers stable ids and reference those ids as selectors.
-
How do I save and reload a design?
Call builder.getData() to get a JSON snapshot and POST it to your own backend. To restore it, call builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl) with those four positional arguments, passing your saved snapshot as the first one. Storage and transport are entirely up to you.
-
Should I call builder.destroy() in onBeforeUnmount?
No. BuilderJS has no destroy(), teardown, or dispose method. When Vue unmounts the component it discards the container DOM nodes and the editor with them. Just avoid instantiating more than one Builder against the same containers — guard with a flag if a double-invoke is possible.
-
Does BuilderJS work with SSR or Nuxt?
The editor is a client-side DOM-mount bundle and never runs during server-side rendering. In Nuxt or any SSR app, keep the new Builder(...) call inside onMounted, which only executes in the browser. The server can render the three container divs; the engine attaches to them on the client.