Vue stays responsible for
Your application, exactly as you build it today.
- Application UI
- Routing
- Authentication
- Business logic
- State management
- APIs and data fetching
- Billing and plans
- Users and permissions
PageKit — the self-hosted GrapesJS site builder, sold as source. Get early access
Build a visual drag-and-drop page builder for Vue 3 and Nuxt with custom blocks, components, templates, responsive editing and your own storage and publishing workflow.
Blocks
Your page
Styles
View the Vue integration guide
0.23.6
GrapesJS version this page is written against
100+
Plugins and templates on GJS.Market
BSD-3-Clause
Licence of the editor core
$0
Cost to run the editor itself
This is GrapesJS itself, not a screenshot. Drag a block in, select an element, restyle it, switch device widths. The editor is created by one `init()` call — in a Vue app that call lives in `onMounted`, and everything you see here behaves identically.
The stock editor with no theme applied. This is what you get before you configure anything — the starting point every builder below was customised from.
Loads an external demo in an iframe.
A production page builder is far more than a few Vue components. It is a canvas, a drag-and-drop system, a component model, a style engine, responsive editing, undo history, asset handling, storage and export. Here is the whole surface, and who ends up owning each piece.
An isolated document users edit directly, with its own styles and scroll.
Drop targets, insertion points, reordering and nesting rules.
Typed elements with their own settings, constraints and behaviour.
Click-to-select, hover outlines, per-element toolbars and badges.
Typography, colour, spacing and layout controls bound to selectors.
Device widths and breakpoint-scoped styles in the same canvas.
A tree of the page so deep structures stay navigable.
A command history across every edit, not just text.
Clean markup and stylesheet generated from the current document.
The ready-made sections users drag in. Yours, or installed.
Whole starting layouts so nobody meets a blank canvas.
Uploads, media libraries and image handling.
Inline text formatting beyond the built-in editor.
More than one page inside a single project.
Where project data is written and read. Your API, your database.
Who may open, edit and publish which project.
Isolating one customer's pages, assets and templates from another's.
Turning approved output into a live page on your infrastructure.
What each customer is entitled to, and what they pay for it.
The middle column is the point of GJS.Market: it is real capability you install rather than schedule. The right-hand column never becomes someone else's problem — that is your product, and this page is explicit about it in every section that touches it.
See the five-step integrationGrapesJS is not a replacement for your application and not a hosted service. It is an editing engine that mounts into one element of one route. Everything above and below it in this diagram stays yours.
Everything that makes the product yours. GrapesJS has no opinion about any of it.
One mounted instance, inside one component, on one route. This is the layer you are not building.
The editor hands you two artefacts and one media stream. What happens next is your API's decision.
Ordinary rows in a schema you designed. Nothing here is a GrapesJS format you have to adopt.
Read it top to bottom: your Vue application authenticates the user and routes them to an editor page; the editor produces project data; your API writes it to your database. The editor never talks to your database, and your database never has to understand the editor.
How the editor mounts into a Vue componentNo. This is the first question most Vue developers ask, and the answer changes how the rest of the page reads. The two layers do not overlap: one runs your application, the other runs an editing surface inside it.
Your application, exactly as you build it today.
The visual editing layer, inside one element you give it.
One honest caveat while we are here: the canvas renders plain DOM inside an iframe, not mounted Vue components. Your app is Vue; what your users edit is HTML and CSS that your app then renders however it likes.
The same engine, configured differently. Each of these is a real product shape teams ship on top of it — and each has a page here that goes deeper.
SaaS
Let your customers create and edit pages inside your product, without leaving it and without a developer.
Explore SaaS buildersWebsites
A full visual website creation experience: multi-page projects, templates, assets and publishing.
See the landing-page angleMarketing
Let marketing teams launch campaign pages on their own schedule instead of yours.
Explore landing buildersContent
Give content teams a visual editing surface over the content model you already have.
See the CMS editorForms
Compose forms visually from typed field components, then post them to your own endpoints.
Browse form pluginsAuthor and manage email templates visually, with output built for mail clients rather than browsers.
See email buildingDocuments
Lay out documents, reports and one-pagers visually, then export or render them from your backend.
See embedded editorsAgencies
Ship every customer a branded editing environment that looks like your product, not like a library.
Explore white-labelThis is what the editor gives you on day one, before a single plugin is installed. Each one is configurable — panels, categories, constraints and labels are all yours to set.
Users build layouts by dragging sections and elements into the canvas, with drop targets, reordering and nesting.
Define your own element types with their own settings, constraints and behaviour, and reuse them across every project.
The ready-to-use sections users pull from the panel. Group them into categories that match your product's vocabulary.
Typography, colour, spacing and layout controls. Restrict the property set so users cannot style their way out of your design system.
Switch the canvas between desktop, tablet and mobile widths and write breakpoint-scoped styles in place.
A structural tree of the page, so a deeply nested layout stays selectable and reorderable.
An asset manager for images and media, backed by whatever storage or CDN you already use.
Reusable starting layouts, so a new page begins as something rather than an empty canvas.
Generate HTML and CSS from the current document, or take the project data and render it your own way.
The difference between a generic website builder and a builder that belongs in your product is the component set. Do not force your content model into someone else's element types — register your own.
Each step is ordinary configuration on the editor instance: a component type, a block that inserts it, a template that arranges several, a command that acts on them, and a panel that exposes it.
// builder/registerProductBlocks.ts
import type { Editor } from 'grapesjs';
// A plugin is just a function that receives the editor, so everything below
// works identically in Vue, Nuxt, React or a plain HTML page.
export function registerProductBlocks(editor: Editor): void {
// A custom component type: your own element, with your own settings.
editor.DomComponents.addType('pricing-table', {
isComponent: (el) => el.dataset?.gjsType === 'pricing-table',
model: {
defaults: {
droppable: false,
traits: [
{ name: 'plan', type: 'select', options: ['free', 'pro', 'team'] },
{ name: 'highlight', type: 'checkbox' },
],
},
},
});
// A block: what the user actually drags in from the panel.
editor.BlockManager.add('pricing-table', {
label: 'Pricing',
category: 'Sections',
content: { type: 'pricing-table' },
});
}Don't force your product into a generic website builder. Customise the editor around your own content model and design system.
An unrestricted canvas produces pages that embarrass the product they live in. The editor is configurable in both directions — the useful setting is not maximum freedom, it is bounded freedom.
Give users creative freedom without letting them break your product's design system.
The single biggest driver of whether a page builder gets used is what is waiting in the block panel on the first visit. Build the sections your product actually needs, or install a library and rename it.
Headline, supporting line and a primary action.
A repeating grid of capabilities or benefits.
Plan columns with features and a call to action.
Quotes with attribution, in a row or a carousel.
Question and answer pairs, expandable or flat.
A form section posting to your own endpoint.
A full-width band with one clear action.
Navigation columns, legal links and brand marks.
GrapesJS does not host anything and does not choose a database. You register a storage adapter with two methods — load and store — and both talk to your API. Nothing leaves your infrastructure.
The editor only ever knows about `load()` and `store()`. Where those calls end up — Postgres, MySQL, Mongo, S3, a headless CMS — is a decision the editor never participates in.
const editor = grapesjs.init({
container: host.value!,
storageManager: {
type: 'app-api',
autosave: true,
stepsBeforeSave: 10,
},
// Registered as a plugin so the adapter exists before the first load().
plugins: [
(ed) => {
ed.Storage.add('app-api', {
async load() {
const res = await fetch(`/api/projects/${props.projectId}`);
if (!res.ok) throw new Error(`Load failed: ${res.status}`);
return await res.json();
},
async store(project) {
const res = await fetch(`/api/projects/${props.projectId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(project),
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
},
});
},
],
});You control your data and your backend. The editor is a client of your API like any other part of your app.
Two artefacts come out of the editor and they are not interchangeable. Storing the wrong one is the mistake that makes pages uneditable six months later.
The full editable document: components, styles, assets, pages. Feed it back to `load()` and the user resumes exactly where they left off. This is the row you must never lose.
A rendering of the current document, produced on demand. Regenerate it on every publish rather than treating it as state; it cannot be turned back into an editable project.
The JSON is what the editor reads. The HTML is what your visitors read. Storing only the second is the one-way door — the page renders forever and can never be edited again.
// The source of truth. Store it; the editor reloads from it.
const projectData = editor.getProjectData();
// The output. Regenerate it every time you publish.
const html = editor.getHtml();
const css = editor.getCss();
await $fetch(`/api/projects/${projectId}/publish`, {
method: 'POST',
body: { projectData, html, css },
});
// Authorize the request and sanitize `html` on the server —
// never in the component that produced it.To edit again
To publish
Store project data for editing. Generate HTML and CSS when you need publishable output.
Authoring and publishing are different jobs. GrapesJS finishes at the moment a user is happy with a page; everything after that is your workflow, on your infrastructure.
Insert review, approval or scheduling wherever your product needs them — the editor has no opinion about the steps between save and live.
Where published output can go
GrapesJS handles authoring. Your infrastructure handles publishing — there is no hosting bundled with the editor, and this page will not pretend otherwise.
Every customer gets their own pages, their own assets, their own templates and their own brand. That isolation is a property of your data model and your API — the editor is simply handed the right project.
Your Vue SaaS
Org A
Org B
Org C
What your Vue app and backend implement
Multi-tenancy is not a GrapesJS feature. Nothing in the editor enforces it, and a page builder that relies on the client to pick the right tenant is not multi-tenant — it is a shared database with a UI convention.
Nothing in the interface has to look like a library. The panels, the icons, the wording and the workflow are all configuration — most shipped GrapesJS builders are unrecognisable as GrapesJS.
Custom logo, colours, typography and panel chrome, applied per customer if you sell to agencies.
Explore white-labelRearrange or replace panels entirely, and drive the editor from your own Vue components and commands.
See embedded editorsRename blocks, categories and actions to the words your users already use for them.
See SaaS packagingTo be clear about what does not exist: there is no official GrapesJS wrapper for Vue and no Vue-specific plugin on GJS.Market. You call `grapesjs.init()` yourself from `onMounted`, which is five lines, and every plugin in the catalogue then works unchanged.
Start at the top and add only the layers your product actually reaches. Most builders never install anything from the bottom half.
Real listings from GJS.Market, grouped by the problem they remove. Every one of them installs into the same editor instance your Vue component creates.
The problem: an empty block panel on first run. What they add: ready-made sections, template management and multi-page projects. Who needs them: anyone whose users are not developers.
Browse this categoryA starting block set, so the panel is useful before you write your own.
Tailwind-based sections for products whose design system is already Tailwind.
Save, list and apply whole layouts as reusable starting points.
Multiple pages inside one project, with navigation between them.
The problem: lost work and hand-rolled persistence. What they add: project management, autosave, crash recovery and alternative storage back ends. Who needs them: every product with real customers in the editor.
Browse this categoryProject listing, creation and switching, instead of one editor per route.
Recovers unsaved work after a crash or a closed tab — the failure users never forgive.
Local persistence in the browser, useful for drafts and offline editing.
Reads and writes projects through a Directus backend rather than a custom API.
The problem: image handling and getting output out of the editor. What they add: upload widgets, CDN integrations, image editing and archive export. Who needs them: builders where users bring their own media.
Browse this categoryUploads and serves editor media through Cloudinary.
A full upload experience — drag-in, progress, multiple sources.
Crop, resize and adjust images inside the editor instead of before it.
Downloads the current project as an HTML/CSS archive.
The problem: the editing surface is thinner than your users expect. What they add: richer text editing, forms, breakpoint control and per-component code. Who needs them: teams replacing an existing editor.
Browse this categoryExtends inline text editing beyond the built-in toolbar.
Form components with typed fields, for builders that collect input.
Manage breakpoints and device widths beyond the three defaults.
Per-component HTML/CSS editing for the users who want it.
The same catalogue, cut by the product you are building. Install the stack that matches your case and ignore the rest — an editor with every plugin loaded is slower and harder to use, not better.
Customers editing their own pages inside your product.
Content teams editing structured content visually.
See the CMS pageFull site creation, multi-section and responsive.
Campaign and transactional templates, not web pages.
See email buildingBounded editing with review, roles and approvals.
Prices shown are the current GJS.Market listing prices; free plugins are marked.
If a stack above does not match your product, start here instead. These are the catalogue sections, roughly in the order a builder grows into them.
Neither column is free. The question is only which one your team should be spending its time in.
| Consideration | Build it in-house | Install from GJS.Market |
|---|---|---|
| Getting the capability | Development time on your roadmap | Install and configure |
| Keeping it working | You maintain it forever | Maintained as a published package |
| Reuse | Internal-only code | A solution other products already run |
| Scope | You build every feature yourself | Add only what your product needs |
| Where the cost lands | Engineering time | A listing price, once |
| What your team works on | Editor infrastructure | Your product |
No cost or time figures appear in this table on purpose — we do not know your team, and any number here would be invented.
Row by row, what you would be writing yourself and what already exists. "Extensible" means the engine provides the hook and you supply the behaviour — usually a few lines, sometimes a plugin.
| Capability | From scratch | GrapesJS |
|---|---|---|
| Editing canvas | Build | Included |
| Drag & drop | Build | Included |
| Component model | Build | Included |
| Blocks | Build | Extensible |
| Style Manager | Build | Included |
| Responsive editing | Build | Included |
| Layer tree | Build | Included |
| Undo & redo | Build | Included |
| Assets | Build | Extensible |
| Templates | Build | Extensible |
| Storage | Build | Extensible |
| HTML / CSS export | Build | Included |
| Vue components in the canvas | Build | Not included — the canvas renders DOM |
| Multi-tenancy, roles, publishing | Build | Not included — your application |
Included = ships with the core. Extensible = a documented extension point, plus plugins where they exist.
Verified against GrapesJS 0.23.6 on 2026-09-02.
Build your Vue product. Don't rebuild the visual editor.
Five steps, one component. There is no official Vue wrapper and you do not need one: GrapesJS renders into a plain element, so the only Vue-specific question is which lifecycle hook calls `init()`. Install grapesjs 0.23.6, create the component, initialise on mount, pass your plugins, destroy on unmount.
npm install grapesjs<!-- components/PageBuilder.vue -->
<template>
<div ref="host" class="page-builder" />
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
import grapesjs, { type Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
// shallowRef, not ref: the editor is a large non-reactive instance and
// deep-proxying it costs performance for nothing.
const editor = shallowRef<Editor | null>(null);
const host = ref<HTMLElement | null>(null);
onMounted(() => {
editor.value = grapesjs.init({
container: host.value!,
height: '100%',
fromElement: false,
storageManager: false,
plugins: [], // GJS.Market plugins go here
blockManager: { blocks: [] },
});
});
onBeforeUnmount(() => {
editor.value?.destroy();
editor.value = null;
});
</script>GrapesJS needs a browser DOM. Under Nuxt's server render there is no `document`, so the editor has to be created on the client — which `onMounted` already guarantees. `<ClientOnly>` is the belt-and-braces version, and it is where the fallback markup lives.
The server renders everything around the editor; the editor itself appears once the component is mounted in the browser.
<!-- pages/editor.vue -->
<template>
<ClientOnly>
<PageBuilder :project-id="route.params.id" />
<template #fallback>
<p class="editor-placeholder">Loading the editor…</p>
</template>
</ClientOnly>
</template>
<script setup lang="ts">
const route = useRoute();
</script>Reproduced, not assumed
Run in plain Node with no DOM on 2026-09-02. The distinction matters: the import is safe, so you do not need a dynamic import to keep the bundle server-safe — you need the init call to happen in the browser.
Each of these is cheap to get right at the start and expensive to fix once customers are in the editor.
The mistake
Calling `init()` anywhere that runs on the server throws, because there is no `document` to render the canvas into.
What to do instead
Create the editor in `onMounted` only, and wrap the route in `<ClientOnly>` if you want a server-rendered fallback.
The mistake
The rendered output cannot be turned back into an editable document. Every page saved this way is frozen the moment it is published.
What to do instead
Store the project JSON as the source of truth and regenerate HTML and CSS whenever you publish.
The mistake
Total freedom produces pages that break your design system, and a first-run experience with nothing in the panel produces no pages at all.
What to do instead
Ship curated blocks and templates, and constrain the style properties users can reach.
The mistake
Each plugin adds panels, bundle weight and a surface your users have to understand. A crowded editor is harder to use, not more capable.
What to do instead
Start with the stack for your product shape and add a plugin when a real need appears.
The things that separate a working demo from something you can put in front of paying customers.
The instance behaves correctly across every route change.
Nothing a user typed can be lost.
A non-developer can produce a good page unaided.
First-run experience decides adoption more than feature count.
Every rule is enforced where the client cannot reach it.
The path from an approved draft to a live URL is defined.
The editor is a heavy dependency and it belongs on exactly one route. Most performance problems in a builder come from loading it where it is not needed.
A page builder accepts arbitrary content from users and turns it into pages other people see. Treat every input as untrusted and every client-side rule as decorative.
Never rely only on client-side permissions.
Different products, one architecture. If you recognise your team here, the rest of this page is written for you.
Adding visual editing to an existing product so customers stop filing tickets for content changes.
Building a custom editing experience per client, on one codebase they maintain.
Giving customers visual editing over content the platform already models.
Letting campaign teams launch pages without a developer in the loop.
Creating controlled internal editing workflows with roles, review and approval.
A visual editor inside a Vue application that lets someone assemble and style a page by dragging sections onto a canvas, instead of writing markup. The Vue app supplies the routes, users and data; the editor supplies the canvas.
Yes. GrapesJS renders into a plain DOM element, so you create it inside `onMounted` and destroy it in `onBeforeUnmount`. No wrapper library is involved.
Yes. The editor is created on the client; `onMounted` already guarantees that, and `<ClientOnly>` gives you a server-rendered fallback around it.
Importing the module is safe on the server — only `init()` requires a DOM. So the surrounding page server-renders normally and the editor appears once the component mounts in the browser.
It is a framework-agnostic editing engine, not a Vue library. It becomes a Vue page builder when you mount it in a Vue component and connect it to your own storage and publishing.
Install the package, add a component with a container element, call `grapesjs.init()` in `onMounted` with your plugins and blocks, and call `destroy()` on unmount. The complete guide covers events, composables and wrappers.
Yes, and it is the natural fit — the lifecycle hooks are exactly where creation and teardown belong. Hold the instance in `shallowRef` rather than `ref`.
No. There is an official React wrapper, but nothing for Vue; the package on npm targets Vue 2 and has not been updated since 2022. Calling `init()` yourself is about five lines and avoids depending on unmaintained code.
Yes. Blocks are registered on the editor's Block Manager with your own markup, label and category, and you can group them however your product's vocabulary works.
Yes. Define your own component types with their own settings, constraints and behaviour, then expose them as blocks. This is how a builder stops looking generic.
No. The canvas is an isolated document rendering plain DOM, so it edits HTML and CSS rather than mounting Vue components. Your application is Vue; the edited page is markup your app can render however it likes.
Yes. Save whole layouts as starting points, either through your own storage or with a template management plugin from the catalogue.
Yes, and that is the intended design. You register a storage adapter with `load` and `store` methods that call your API — the editor never talks to your database directly.
Store the project JSON: it is the editable source of truth the editor reloads. Generate HTML and CSS when you publish. Storing only HTML makes the page permanently uneditable.
Yes. Your Vue app handles accounts, plans and routing; the editor handles authoring; your API handles projects. That combination is the common shape for a builder sold as a product.
Yes, but the tenancy lives in your application and your database, not in the editor. Scope every project, asset and template by tenant and enforce it server-side.
Yes. Logo, colours, panels, terminology and available blocks are all configuration, so each customer can be given an environment that looks like your product.
Yes — it is the most common first version. Curate a small set of marketing sections, add templates, and let the marketing team publish through your existing pipeline.
Yes. Register component types that mirror your content model, so editors work with your entities visually rather than with free-form markup.
Yes. A plugin is a function that receives the editor instance, so the whole GJS.Market catalogue works in a Vue app unchanged — there is no Vue-specific edition to look for.
Yes. The core is BSD-3-Clause licensed, which permits commercial use, including inside a product you sell. Check the individual licence of any plugin you install.
Need help integrating GrapesJS with Vue or Nuxt, creating custom blocks and components, connecting your backend or building a production-ready editing workflow? We do that work.
For teams that would rather ship the product than the editor infrastructure.
Use Vue for your application and GrapesJS for the visual editing experience. Add your own components, blocks, templates, storage and publishing workflow — then extend the builder with GJS.Market plugins when you need more.
Five steps and one component. Install the package and have an editor running in your Vue app this afternoon.
Get StartedBlocks, templates, storage, assets and export — install the capabilities you would otherwise put on the roadmap.
Browse PluginsIntegration, custom components, backend wiring and a production editing workflow, built with your team.
Talk to usYour Vue app. Your editor. Your product.