PageKit — the self-hosted GrapesJS site builder, sold as source. Get early access

GrapesJS + Vue 3

GrapesJS Vue: Build a Visual Editor with Vue 3

Integrate the open-source GrapesJS visual editor into Vue 3 and Nuxt applications to build visual page builders, CMS editors, landing page builders, email editors, and white-label editing experiences.

Vue 3Composition APITypeScriptOpen sourceHTML & CSSExtensible
Where the editor sits

Vue powers your application. GrapesJS powers the editing.

The split is worth naming before any code. Your Vue application owns routing, users, permissions and data. GrapesJS owns the canvas and everything a person manipulates inside it. The two meet at one DOM node and one save call.

  1. Vue 3 application
  2. GrapesJS editor
  3. HTML + CSS + project data
  4. Your API / CMS / SaaS

Nothing in that chain requires Vue to know how the editor works, and nothing requires GrapesJS to know it is inside a Vue app. That is why the integration is short.

Short answer

Can You Use GrapesJS with Vue?

Yes. GrapesJS is framework-agnostic and can be integrated directly into Vue 3 applications. You can initialize the editor with Vue lifecycle hooks, keep the editor instance in a composable or component, connect GrapesJS events to your Vue application, and persist project data through your own backend.

Two ways to do it

Direct integration

Import grapesjs, call grapesjs.init() in onMounted(), call editor.destroy() in onUnmounted(). No extra dependency, nothing between you and the editor API.

A Vue-oriented layer

Wrap that lifecycle in your own composable — or a community package — so components get a Vue-shaped API instead of an imperative one. Useful once more than one screen mounts an editor.

GrapesJS publishes one official framework wrapper, @grapesjs/react, for React. There is no equivalent Vue package from the GrapesJS project, so any Vue wrapper you find on npm is a community project. Check its Vue and GrapesJS peer ranges before you adopt it.

The division of labour

What Is GrapesJS for Vue?

Vue 3 is an application framework. GrapesJS is a visual editing engine. They solve different problems, and the integration is clean precisely because they do not overlap.

Your Vue code
  • Routing
  • Authentication
  • Permissions
  • Application UI
  • API and data layer
In GrapesJS
  • Canvas
  • Drag & drop
  • Component tree
  • Block Manager
  • Style Manager
  • Device Manager
  • UndoManager
  • Commands
  • Serialization
Plugin or adapter
  • Asset Manager
  • Storage Manager
  • Rich Text Editor
  • Export

Five of these are yours. The rest already exist, and the two remaining are configuration rather than construction.

Step 1

Install GrapesJS in Vue 3

One dependency. GrapesJS ships its own TypeScript definitions, so there is no separate @types package to add.

npm install grapesjs

Import the stylesheet

The editor is unstyled without it. In a Vite or Nuxt project you can import it from the component or composable that creates the editor; in an app with a global stylesheet, import it once there instead.

What the integration needs

  • A DOM element for the canvas, reached through a template ref
  • grapesjs.init() called after that element exists
  • editor.destroy() called when the component goes away
  • The GrapesJS stylesheet loaded somewhere
  • Browser-only execution — see the Nuxt section
Step 2

A Minimal Working Vue 3 Example

Copy this into a component and it runs. Everything after this section is an addition to it, not a rewrite of it.

VisualEditor.vueVUE
<template>
  <div ref="editorContainer" class="editor-shell"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import grapesjs, { type Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

// A template ref: the name matches the ref="" attribute above.
const editorContainer = ref<HTMLDivElement | null>(null);

// shallowRef, not ref — see "Performance" further down the page.
const editor = shallowRef<Editor | null>(null);

onMounted(() => {
  if (!editorContainer.value) return;

  editor.value = grapesjs.init({
    container: editorContainer.value,
    height: '100%',
    fromElement: false,
    storageManager: false,
  });
});

onUnmounted(() => {
  editor.value?.destroy();
  editor.value = null;
});
</script>

<style scoped>
.editor-shell {
  height: 100vh;
}
</style>

Three things worth noticing

  1. The template ref name matches the ref="" attribute — that is how Vue binds them in <script setup>.

  2. The editor lives in shallowRef, not ref. Vue would otherwise walk the entire editor object graph making it reactive.

  3. fromElement is false, so GrapesJS starts from the components you pass rather than from whatever markup was inside the container.

Lifecycle

GrapesJS with the Vue 3 Composition API

The whole integration is a lifecycle mapping. Vue tells you when the DOM node exists and when it is about to disappear; GrapesJS needs exactly those two moments.

VueGrapesJS
onMounted()
grapesjs.init()
The container element exists now, and only now. Initialising earlier gives GrapesJS a null container.
onUnmounted()
editor.destroy()
Removes listeners, the canvas iframe and the editor DOM. Skipping it leaks on every route change.
ref()
container element
A template ref is the handle GrapesJS mounts against.
shallowRef()
editor instance
Holds the instance without making it deeply reactive.

Read top to bottom, that is the entire contract between the two libraries.

Neither onMounted nor onUnmounted runs on the server, which is why a plain Vue SPA needs nothing extra. A server-rendered app needs one more guard — the Nuxt section covers it.

Reusable

Create a Reusable useGrapesJS Composable

Once a second screen needs an editor, move the lifecycle into a composable. The component then says what it wants, not how the editor is wired.

useGrapesJS.tsTS
// composables/useGrapesJS.ts
import { onMounted, onUnmounted, shallowRef, type Ref } from 'vue';
import grapesjs, { type Editor, type EditorConfig } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

type EditorOptions = Omit<EditorConfig, 'container'>;

export function useGrapesJS(
  container: Ref<HTMLElement | null>,
  options: EditorOptions = {},
) {
  const editor = shallowRef<Editor | null>(null);

  const init = (): Editor | undefined => {
    if (editor.value || !container.value) return;

    editor.value = grapesjs.init({
      container: container.value,
      height: '100%',
      fromElement: false,
      storageManager: false,
      ...options,
    });

    return editor.value;
  };

  const destroy = (): void => {
    editor.value?.destroy();
    editor.value = null;
  };

  onMounted(init);
  onUnmounted(destroy);

  return { editor, init, destroy };
}

Using it

EditorScreen.vueVUE
<template>
  <div ref="editorContainer" class="editor-shell"></div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useGrapesJS } from '~/composables/useGrapesJS';

const editorContainer = ref<HTMLElement | null>(null);

const { editor } = useGrapesJS(editorContainer, {
  blockManager: {
    blocks: [
      {
        id: 'hero',
        label: 'Hero',
        category: 'Sections',
        content: '<section>…</section>',
      },
    ],
  },
});
</script>

What the composable buys you

  • One integration to maintain instead of one per screen
  • Configuration in a single place
  • Editor setup that can be tested on its own
  • Several editors in one app without copy-paste
  • Event handling shared across components
  • Components that read as Vue, not as DOM plumbing

Resist growing this further than it needs to be. A composable that wraps every GrapesJS module in a reactive facade is a second API to learn and a second thing to keep in sync.

Reactivity

Connecting GrapesJS Events to Vue

GrapesJS manages editor state. Vue manages application state. Events are the seam: subscribe to the few that your UI actually reflects, and write them into ordinary refs.

editor-events.tsTS
import { onMounted, ref, shallowRef } from 'vue';
import type { Editor, Component } from 'grapesjs';

const isReady = ref(false);
const isDirty = ref(false);
const saveState = ref<'idle' | 'saving' | 'saved' | 'error'>('idle');
const selectedName = ref<string | null>(null);

function bindEditorEvents(editor: Editor): void {
  editor.on('load', () => {
    isReady.value = true;
  });

  editor.on('component:selected', (component: Component) => {
    selectedName.value = component?.getName?.() ?? null;
  });

  editor.on('component:update', () => {
    isDirty.value = true;
  });

  editor.on('storage:start:store', () => {
    saveState.value = 'saving';
  });

  editor.on('storage:end:store', () => {
    saveState.value = 'saved';
    isDirty.value = false;
  });

  editor.on('storage:error', () => {
    saveState.value = 'error';
  });
}
EventWhat your UI does with it
loadHide the loading state once the project is on the canvas
component:selectedShow which element is selected in your own toolbar
component:updateMark the document dirty, enable the save button
storage:start:storeShow a saving indicator
storage:end:storeShow saved, clear the dirty flag
storage:errorSurface a failure the user can act on

Mirror what you display, not what the editor holds. Copying the component tree into a Vue store gives you two sources of truth and a synchronisation bug.

Nuxt & SSR

Using GrapesJS with Nuxt

GrapesJS relies on browser DOM APIs, so editor initialization should happen only in a browser context. In Nuxt that means keeping the editor out of the server render — everything else is the same integration you already have.

pages/editor.vueVUE
<!-- pages/editor.vue -->
<template>
  <ClientOnly>
    <VisualEditor />

    <template #fallback>
      <p class="editor-placeholder">Loading the editor…</p>
    </template>
  </ClientOnly>
</template>
components/VisualEditor.client.vueVUE
<!-- components/VisualEditor.client.vue -->
<template>
  <div ref="editorContainer" class="editor-shell"></div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useGrapesJS } from '~/composables/useGrapesJS';

// Reached only in the browser: the .client suffix keeps this
// component out of the server render, and onMounted never runs
// on the server anyway.
const editorContainer = ref<HTMLElement | null>(null);
const { editor } = useGrapesJS(editorContainer);
</script>

Two correct options

<ClientOnly>

Wrap the editor where it is used. The fallback slot gives the server something to render, so there is no blank frame before hydration.

A .client component

Nuxt's own naming convention: a component whose filename ends in .client is only ever rendered in the browser. Useful when the editor is used in several places.

Both are valid, and they combine. Neither is required by GrapesJS — what GrapesJS requires is simply that init() runs where document exists.

Architecture

Where the boundary falls

SSR renders the application shell. GrapesJS runs in the browser. Naming that boundary is what makes a Nuxt integration predictable instead of a source of hydration bugs.

  1. Nuxt application
  2. SSR / application shell
  3. Client-only visual editor
  4. GrapesJS
  5. API / CMS / database

The editor screen is not a page you server-render for SEO — it is an authenticated tool. The pages it produces are what you render and index, and those are plain HTML and CSS.

Build a complete Vue page builder
Decision

Do You Need a Vue Wrapper for GrapesJS?

No. Direct integration is a complete, supported way to use GrapesJS, and it is what the examples on this page do. A wrapper is a convenience, not a requirement.

ApproachBest forTrade-off
Direct GrapesJSMaximum controlYou write the lifecycle yourself — about fifteen lines.
Vue wrapper / integrationA more Vue-oriented APIA dependency between you and the editor API, on someone else’s release schedule.
Custom composableReusable application integrationYours to maintain — but it is the file shown above, and it does not go stale.

What is actually published

Worth checking before you adopt anything, because the search results for this question are older than the packages they describe.

Official React wrapper
@grapesjs/react 2.0.0, MIT, published by the GrapesJS maintainers.
Official Vue wrapper
None. The GrapesJS project does not publish a Vue package.
Community Vue package
vue-grapesjs 0.1.0 is a third-party project, last published 2022-05-23, with peer ranges vue@^2.6.6 and grapesjs@^0.14.55 — Vue 2 and a much older GrapesJS.

Checked against the npm registry on 2026-09-02. If you find a newer Vue package, read its peerDependencies first: that single field tells you whether it targets Vue 3 and a current GrapesJS.

For Vue 3 today, the composable above is the wrapper. It is twenty-odd lines, it uses the GrapesJS API directly, and it cannot fall behind a release you depend on.

Beyond the integration

Build a Vue Page Builder with GrapesJS

Mounting the editor is the first afternoon. A page builder your users trust is the rest of the work, and it is mostly configuration of the modules GrapesJS already exposes.

  • Canvas

    The editable surface and its device widths.

  • Components

    The types your users can place, and their traits.

  • Blocks

    The palette they drag from.

  • Style Manager

    Which CSS properties are exposed, and to whom.

  • Asset Manager

    Images and media, from your storage.

  • Storage Manager

    Where project data is loaded and saved.

  • Commands

    Named actions, bindable to your own UI.

  • Device Manager

    Responsive breakpoints for editing and output.

  • Plugins

    Everything above, packaged and reusable.

Learn how to build a complete Vue page builder
What you can build

What Can You Build with GrapesJS and Vue?

The same editor core, configured differently. Each of these is a different set of blocks, a different storage target and a different publish step.

Output

What Does GrapesJS Store and Generate?

Three things that are easy to conflate, and expensive to conflate. Getting this right on day one is what makes a document re-editable a year later.

Project data

The editable state of the GrapesJS project — pages, components, styles, assets. Read it with editor.getProjectData(), restore it with editor.loadProjectData(). This is the thing to persist.

HTML

The rendered markup, from editor.getHtml(). Regenerate it whenever you publish. It is output, not source.

CSS

The styles the editor produced, from editor.getCss(). Same rule: generated, not stored as the source of truth.

  1. User edits
  2. GrapesJS project data
  3. Your API / database
  4. HTML + CSS
  5. Preview / publish

Store editable project data. Generate HTML and CSS when you need to render or publish.

Saving only the rendered HTML is the mistake that cannot be undone later. You can always regenerate HTML from project data; you cannot reliably recover project data from HTML.

publish.tsTS
// Editable state — store this, it is what the editor reloads.
const projectData = editor.getProjectData();

// Rendered output — regenerate this whenever you publish.
const html = editor.getHtml();
const css = editor.getCss();

await fetch(`/api/projects/${projectId}/publish`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ projectData, html, css }),
});

// Sanitize `html` on the server before it is served to anyone.
Persistence

Save GrapesJS Projects in Your Vue Application

GrapesJS does not care what your backend is. A storage adapter is two async functions — one that returns project data, one that accepts it — so the editor talks to the API you already have.

  1. Vue
  2. GrapesJS storage
  3. REST API
  4. Backend
  5. Database
storage.tsTS
import grapesjs, { type ProjectData } from 'grapesjs';

// Wherever your app keeps it — a route param, a prop, a store.
const projectId = props.projectId;

const editor = grapesjs.init({
  container: editorContainer.value!,
  fromElement: false,
  storageManager: {
    type: 'app-api',
    autosave: true,
    stepsBeforeSave: 10,
  },
  // Registered as a plugin so the adapter exists before
  // GrapesJS performs its first load.
  plugins: [
    (ed) => {
      ed.Storage.add('app-api', {
        async load(): Promise<ProjectData> {
          const res = await fetch(`/api/projects/${projectId}`);
          if (!res.ok) {
            throw new Error(`Load failed: ${res.status}`);
          }
          return await res.json();
        },

        async store(data: ProjectData) {
          const res = await fetch(`/api/projects/${projectId}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ projectData: data }),
          });
          if (!res.ok) {
            throw new Error(`Save failed: ${res.status}`);
          }
        },
      });
    },
  ],
});

What the adapter covers

load()
Fetch the project when the editor opens.
store()
Send project data to your endpoint.
autosave
Fires after a set number of changes, via stepsBeforeSave.
Project JSON
The serialized state — the thing you version.
Versioning
Keep prior payloads server-side to support rollback.
Publishing
A separate endpoint that renders and sanitizes output.

Registering the adapter through the plugins array matters: it guarantees the storage exists before GrapesJS performs its first load.

Media

Manage Assets in Your Vue Application

The asset manager is a UI over an endpoint. Point it at yours and every existing rule — auth, validation, quotas, CDN — keeps applying.

assets.tsTS
// Fetched from your own API before the editor is created.
const assets = await fetch('/api/assets').then((res) => res.json());

const editor = grapesjs.init({
  container: editorContainer.value!,
  assetManager: {
    assets,

    // Uploads go to your endpoint, with your auth and
    // your validation.
    upload: '/api/assets',
    uploadName: 'files',
    multiUpload: true,
    autoAdd: true,
    headers: { Authorization: `Bearer ${token.value}` },
    credentials: 'same-origin',

    // Reject what the backend would reject anyway,
    // before the round trip.
    beforeUpload: (files: File[]) => {
      const limit = 5 * 1024 * 1024;
      const tooBig = files.some((f) => f.size > limit);
      if (tooBig) {
        toast.error('Images must be under 5 MB');
        return false;
      }
    },
  },
});
upload
Your endpoint; uploadName is the form field it posts to.
assets
Seed the manager with URLs your API already knows about.
headers / credentials
Sent with every upload, so your existing auth keeps applying.
beforeUpload
Rejects client-side; your server still validates.
Storage & CDN
GrapesJS never sees where the bytes land — it stores the URL you return.
Asset URLs
What ends up in the published HTML, so serve them from a durable origin.
Your vocabulary

Create Custom GrapesJS Components for Vue Applications

A custom component type is how the editor learns your product. It gets its own settings panel, its own drop rules and its own block in the palette.

pricing-card.tsTS
import type { Editor } from 'grapesjs';

export function pricingCardPlugin(editor: Editor): void {
  editor.Components.addType('pricing-card', {
    isComponent: (el) => el.dataset?.gjsType === 'pricing-card',
    model: {
      defaults: {
        tagName: 'section',
        attributes: { 'data-gjs-type': 'pricing-card' },
        // Traits become the settings panel for this component.
        traits: [
          { type: 'text', name: 'plan', label: 'Plan name' },
          { type: 'text', name: 'price', label: 'Price' },
          { type: 'checkbox', name: 'featured', label: 'Featured' },
        ],
        components: `
          <h3 data-gjs-type="text">Starter</h3>
          <p data-gjs-type="text">$0 / month</p>
          <a href="#">Choose plan</a>
        `,
      },
    },
  });

  editor.Blocks.add('pricing-card', {
    label: 'Pricing card',
    category: 'Marketing',
    content: { type: 'pricing-card' },
  });
}

// Pass it to the editor like any other plugin:
// grapesjs.init({ container, plugins: [pricingCardPlugin] })

Typical first five

  • Hero
  • Pricing card
  • Product card
  • Form
  • Navigation

Custom components are also how you constrain the editor. Setting droppable or draggable on a type is what stops a user dropping a pricing card inside a navigation bar, and traits are what give them a labelled field instead of a raw CSS control.

These are GrapesJS component types, not Vue components. The canvas renders DOM; your Vue components render your application around it.

Ecosystem

Extend Your Vue Editor with GrapesJS Plugins

A plugin is a function that receives the editor — the same function signature whether it came from npm, from GJS.Market, or from your own repository. None of them care that the host application is Vue.

plugins.tsTS
import grapesjs from 'grapesjs';
import basicBlocks from 'grapesjs-blocks-basic';
import { pricingCardPlugin } from '~/editor/pricing-card';

const editor = grapesjs.init({
  container: editorContainer.value!,
  plugins: [
    // Options go through a wrapper function: `pluginsOpts`
    // is keyed by string, so it only reaches plugins that
    // were passed to `plugins` by name.
    (ed) => basicBlocks(ed, { flexGrid: true }),
    pricingCardPlugin,
  ],
});

Build vs adopt

Build the Visual Editor Yourself or Use GrapesJS?

Not an argument that building is wrong — an inventory of what building means. Every row is a subsystem that exists whether or not you planned for it.

CapabilityBuild yourselfGrapesJS
CanvasBuildIncluded
Drag & dropBuildIncluded
ComponentsBuildIncluded
BlocksBuildIncluded
Style ManagerBuildIncluded
Asset ManagerBuildExtensible
Storage ManagerBuildExtensible
PluginsBuild an ecosystemPlugin architecture
Vue integrationNativeFramework-agnostic

Vue gives you the application framework. GrapesJS gives you the visual editing engine.

Why Not Build the Editor Entirely with Vue Components?

Because Vue components are application building blocks, and a visual editor needs something else on top of them: canvas management, a component tree with drop rules, selection and hover state, a style layer that writes real CSS, device switching, undo and redo across all of it, asset management, serialization, named commands, and an extension point for the whole thing. You can build that in Vue — it is simply a different project from the one you set out to do.

Vue + GrapesJS is a different proposition from Vue components alone.

In production

Production Architecture for GrapesJS + Vue

How the editor sits inside a real application, once there are users, permissions and more than one project.
  1. Vue / Nuxt

    Your Nuxt or Vue application: the shell, the session, and who is allowed to open which project.

    • Application UI
    • Authentication
    • Users
    • Permissions
  2. GrapesJS

    The editor, mounted on one screen. It receives a project and emits a project.

    • Canvas
    • Components
    • Blocks
    • Styles
    • Assets
  3. Application API

    Your own routes. GrapesJS calls them through the storage and asset adapters you wrote.

  4. Backend

    Where projects, media, users and versions actually live — unchanged by the editor above it.

    • Projects
    • Assets
    • Users
    • Versions

The editor is a leaf in that tree, not the root. Everything above it is code you would have written anyway.

Performance

Performance Considerations

  • Do not make the editor deeply reactive

    This is the one that actually bites. shallowRef, or a plain module-scoped variable — never ref() around the editor instance. Vue would walk a very large object graph and re-run effects on internal churn.

  • Initialize only when needed

    Create the editor on the screen that edits, not in a layout or a store that every route mounts.

  • Lazy-load the editor

    GrapesJS is a substantial bundle. Load it in its own async chunk so routes that never edit never pay for it.

  • Keep updates out of global state

    Pushing every component:update into a store turns each keystroke into an application-wide render.

  • Paginate large asset collections

    Seed the asset manager from a paged endpoint rather than shipping thousands of URLs into the editor.

  • Keep component trees manageable

    Very deep documents cost more to render, serialize and diff. Structure encourages itself if your blocks do.

Security

Security Considerations

A visual editor is a system that accepts user-authored HTML. Treat it that way.

Sanitize published HTML
Sanitize on the server, at publish time, before anything is served to a visitor.
Validate uploads
Type, size and content — client-side checks are UX, not enforcement.
Authenticate storage endpoints
Load and save are ordinary API routes and need ordinary auth.
Enforce permissions server-side
Hiding a panel in the editor is not an authorization model.
Validate project data
It arrives from a client and can be anything. Check it before you store it.
Never trust editor state
Anything the browser sends about what was edited is a claim, not a fact.
Protect publishing endpoints
Publishing changes what the public sees; rate-limit and authorize it accordingly.
Troubleshooting

Common GrapesJS + Vue Mistakes

Almost every integration issue reported for this combination is one of these seven.

Initializing before mount

Symptom

The container is null, or the editor renders into nothing.

Fix

Create the editor inside onMounted(). The template ref has no element before then.

Forgetting cleanup

Symptom

Memory grows and stale listeners fire after leaving the route.

Fix

Call editor.destroy() in onUnmounted() and clear the reference.

Making the entire editor deeply reactive

Symptom

The editor feels sluggish; Vue devtools stalls.

Fix

Hold the instance in shallowRef, or outside Vue reactivity entirely.

Initializing GrapesJS during SSR

Symptom

document is not defined, or a hydration mismatch on the editor route.

Fix

Render the editor client-only — <ClientOnly>, a .client component, or both.

Mixing editor state with application state

Symptom

Two sources of truth, and edits that disappear on navigation.

Fix

Let GrapesJS own the project; mirror only the few values your UI displays.

Forgetting the CSS import

Symptom

The editor loads, but the panels are unstyled and unusable.

Fix

Import grapesjs/dist/css/grapes.min.css, or include it in your global stylesheet.

Copying outdated wrapper examples

Symptom

Type errors, or an integration built on Vue 2 lifecycle hooks.

Fix

Check peerDependencies before adopting any wrapper. Several popular examples predate Vue 3.

On Vue 2 the same integration uses mounted() and beforeDestroy() instead of onMounted() and onUnmounted(); the GrapesJS side is unchanged. New work should target Vue 3 — everything on this page assumes it.

Before you ship

Vue + GrapesJS Production Checklist

The order roughly matches the order this page introduced them.

  1. Install GrapesJS
  2. Initialize after mount
  3. Destroy on unmount
  4. Import the GrapesJS CSS
  5. Handle SSR / client-only execution
  6. Decide direct integration vs wrapper
  7. Create a reusable composable where useful
  8. Connect the editor events your UI reflects
  9. Configure storage
  10. Configure assets
  11. Add custom components
  12. Add the plugins your product needs
  13. Implement permissions server-side
  14. Sanitize published content
  15. Test responsive editing
  16. Test the production build
FAQ

GrapesJS and Vue: Frequently Asked Questions

Can I use GrapesJS with Vue 3?

Yes. GrapesJS is a framework-agnostic library that renders into a DOM element, so it works in Vue 3 without an adapter. Initialize it in onMounted() and destroy it in onUnmounted().

How do I install GrapesJS in Vue?

Run npm install grapesjs. The package includes its own TypeScript definitions. You also need to load its stylesheet, either by importing grapesjs/dist/css/grapes.min.css or by including it in your global CSS.

How do I initialize GrapesJS in Vue 3?

Create a template ref on the container element, then call grapesjs.init({ container: containerRef.value }) inside onMounted(). The element does not exist before mount, so initializing any earlier gives GrapesJS nothing to render into.

Can I use GrapesJS with the Composition API?

Yes, and it is the recommended way. onMounted() and onUnmounted() map directly onto grapesjs.init() and editor.destroy(), and the whole integration fits in a single composable.

Can I use GrapesJS with <script setup>?

Yes. Every example on this page uses <script setup lang="ts">. A template ref declared with ref() binds automatically to the matching ref="" attribute.

Does GrapesJS work with Nuxt?

Yes. The integration is identical to plain Vue 3; the only addition is keeping the editor out of the server render, with <ClientOnly> or a .client component.

Does GrapesJS work with Nuxt SSR?

Yes, with the editor rendered client-only. GrapesJS needs browser DOM APIs, so it cannot run during a server render — but the pages it produces are plain HTML and CSS, and those are exactly what you server-render and index.

Do I need a Vue wrapper for GrapesJS?

No. Direct integration is a complete approach and is what the examples here use. GrapesJS publishes an official React wrapper but no Vue equivalent, so any Vue wrapper on npm is a community project — check its Vue and GrapesJS peer ranges before adopting it.

Can I use GrapesJS with TypeScript?

Yes. GrapesJS ships TypeScript definitions in the package, so the Editor type and the configuration object are typed without an extra @types dependency.

Can I save GrapesJS projects to my Vue backend?

Yes. Register a storage adapter with editor.Storage.add() that implements load() and store(). Both are ordinary async functions, so they can call whatever API you already have.

Can I create custom components with GrapesJS?

Yes. editor.Components.addType() defines a component type with its own traits and drop rules, and editor.Blocks.add() puts it in the palette. That is how the editor learns your product’s vocabulary.

Can I use GrapesJS plugins with Vue?

Yes. A GrapesJS plugin is a function that receives the editor instance, so it is unaffected by the framework hosting it. Pass plugins in the plugins array of grapesjs.init().

Can I build a Vue page builder with GrapesJS?

Yes — that is the common case. Mounting the editor is the short part; the rest is configuring blocks, storage, assets and permissions around it.

Build Your Vue Visual Editor with GrapesJS

Integrate the visual editing engine into your Vue or Nuxt application, keep control of your data and output, and extend the editor with the plugins your product needs.

Build your application with Vue 3. Add visual editing with GrapesJS.

Keep reading

Related guides