How to integrate GrapesJS into a Svelte app (complete guide 2026)

A step-by-step guide to embedding the GrapesJS drag-and-drop editor in a SvelteKit app — install, mount it client-side, persist content, and export clean HTML/CSS.

DevFuture Development
DevFuture Development
May 13, 2026a month ago
12 min read4 views

Why pair GrapesJS with Svelte

GrapesJS is a framework-agnostic, MIT-licensed drag-and-drop builder, so it drops into a SvelteKit app without a wrapper library. The only rule to respect is that GrapesJS needs the DOM — it must run in the browser, not during server-side rendering. Svelte's onMount lifecycle makes that trivial: it only fires on the client, which is exactly where the editor belongs.

This guide covers a production-ready setup: install, mount the editor safely, persist content to your backend, and export clean HTML and CSS.

1. Install GrapesJS

npm install grapesjs

No Svelte-specific adapter is required. You can add presets later (for example grapesjs-preset-webpage or the plugins from GJS.Market) once the core editor runs.

2. Mount the editor client-side

Create a route, e.g. src/routes/editor/+page.svelte. Bind a container element, then initialise GrapesJS inside onMount and return its teardown so the instance is destroyed on navigation.

<script>
  import { onMount } from 'svelte';
  import grapesjs from 'grapesjs';
  import 'grapesjs/dist/css/grapes.min.css';

  let container;       // bound to the editor div
  let editor;          // GrapesJS instance

  onMount(() => {
    editor = grapesjs.init({
      container,
      height: '100vh',
      fromElement: false,
      storageManager: false, // we wire storage manually in step 3
      components: '<h1>Hello from GrapesJS</h1>',
      style: 'h1 { font-family: sans-serif; }',
    });

    // Clean up listeners + DOM when the component unmounts.
    return () => editor?.destroy();
  });
</script>

<div bind:this={container}></div>

Because onMount never runs on the server, the editor is created only in the browser — no SSR errors, no window is not defined.

3. Persist content to your backend

The simplest approach is a save button that reads the current document and POSTs it to your API:

<script>
  async function save() {
    const payload = {
      html: editor.getHtml(),
      css: editor.getCss(),
      // Full editable project (for re-loading into the editor later):
      project: editor.getProjectData(),
    };
    await fetch('/api/pages', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
  }
</script>

<button on:click={save}>Save</button>

Prefer automatic saves? Configure the Storage Manager instead of storageManager: false:

storageManager: {
  type: 'remote',
  stepsBeforeSave: 3,
  options: {
    remote: {
      urlStore: '/api/pages',
      urlLoad: '/api/pages/load',
    },
  },
}

4. Export production HTML and CSS

editor.getHtml() and editor.getCss() return the rendered markup and styles as strings. Combine them when you need a standalone document:

const html = editor.getHtml();
const css = editor.getCss();

const document = `<!doctype html>
<html>
  <head><style>${css}</style></head>
  <body>${html}</body>
</html>`;

Reload a saved project

To re-open a page in the editor, pass the stored project data back in via editor.loadProjectData(saved.project) after init, or set projectData in the init config.

Prerequisites

Before you start, make sure you have Node.js 18 or newer and a SvelteKit project created with npm create svelte@latest. You don't need any Svelte-specific GrapesJS wrapper — GrapesJS is framework-agnostic and ships as a plain ES module, so the same approach works in a SvelteKit app, a plain Svelte SPA, or a Svelte component embedded in a larger page. A basic understanding of Svelte's lifecycle (onMount, onDestroy) and of where code runs (server vs browser) is all you need to follow along.

Add custom blocks to the editor

A bare editor isn't very useful until your users have building blocks to drag onto the canvas. Register them with the Block Manager right after grapesjs.init. Each block is a label plus the HTML (or a component definition) it drops onto the page:

editor.BlockManager.add('hero', {
  label: 'Hero section',
  category: 'Sections',
  content: `
    <section class="hero">
      <h1>Headline</h1>
      <p>Supporting copy goes here.</p>
      <a href="#">Call to action</a>
    </section>`,
});

editor.BlockManager.add('two-cols', {
  label: 'Two columns',
  category: 'Layout',
  content: '<div class="row"><div class="cell"></div><div class="cell"></div></div>',
});

Group related blocks with the category property so the panel stays organised, and reach for ready-made block libraries and presets from GJS.Market when you want a richer starting set without building every block by hand.

Persisting to a real backend (deep-dive)

In a SvelteKit app the natural place to receive a save is a +server.js endpoint. Wire the Storage Manager to it and send your auth token in the request headers:

// in grapesjs.init(...)
storageManager: {
  type: 'remote',
  autosave: true,
  stepsBeforeSave: 3,
  options: {
    remote: {
      urlStore: '/api/pages',
      urlLoad: '/api/pages',
      headers: { Authorization: `Bearer ${token}` },
    },
  },
}
// src/routes/api/pages/+server.js
import { json } from '@sveltejs/kit';

let page = {};                     // swap for your database

export async function GET() {
  return json(page);
}

export async function POST({ request }) {
  page = await request.json();     // the full GrapesJS project
  return json({ status: 'ok' });
}

For full control you can register a custom storage adapter instead of the remote preset — useful when you need optimistic UI, offline queues, or a non-HTTP store:

editor.Storage.add('svelte-store', {
  async load() {
    const res = await fetch('/api/pages');
    return res.ok ? res.json() : {};
  },
  async store(data) {
    await fetch('/api/pages', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
  },
});
// then: storageManager: { type: 'svelte-store' }

Performance tips

GrapesJS is a sizeable dependency, so load it only where it's needed. Importing it dynamically inside onMount (await import('grapesjs')) keeps it out of your main bundle and out of the server render path. Always tear the editor down with editor.destroy() when the component unmounts — during SvelteKit's client-side navigation a leaked instance keeps its listeners and DOM nodes alive, and they accumulate. Tune stepsBeforeSave so autosave isn't firing on every tiny change, and lazy-load heavy plugins only when a user actually opens the feature that needs them.

Security considerations

GrapesJS faithfully stores whatever markup is on the canvas, so treat exported HTML as untrusted if non-admin users can edit pages: sanitise it on output (a vetted server-side sanitiser) before rendering it to other visitors. Protect the save endpoint with real authentication and authorisation — never accept an unauthenticated POST that overwrites a page. If you load third-party plugins into the editor, pin their versions and review them, since they run with full access to the editor and the page.

Troubleshooting common errors

“document is not defined” means GrapesJS ran during SSR — move the import and init inside onMount. The canvas looks unstyled means the stylesheet import is missing; add import 'grapesjs/dist/css/grapes.min.css'. The editor is blank usually means the container element wasn't bound yet when init ran — bind it with bind:this and init in onMount. Saves fail with 413 means the project payload exceeds your server's body-size limit; raise it on the endpoint.

When to use GrapesJS with Svelte

GrapesJS is the right call when you need to embed a real visual page or email builder inside your Svelte product — a SaaS page editor, a CMS surface, a newsletter composer — and you want to own the editor, its storage, and its HTML output. If all you need is rich-text editing inside a single field, a lighter rich-text component is a better fit. For full-page composition with drag-and-drop layout, a style manager, and clean HTML/CSS export, GrapesJS gives you far more than a text editor while staying MIT-licensed and self-hosted.

Common pitfalls in SvelteKit

Developer debugging JavaScript code on a laptop
Most GrapesJS + SvelteKit issues come down to where the editor is initialised.

Three mistakes account for almost every broken setup. First, importing GrapesJS at the top of the module instead of inside onMount — that runs it during SSR and throws document is not defined. Keep the import lazy (await import('grapesjs')) inside the lifecycle hook. Second, forgetting to return editor.destroy(): with SvelteKit's client-side routing the component remounts often, and leaked editor instances pile up listeners and memory. Third, missing the stylesheet — without grapesjs/dist/css/grapes.min.css the canvas renders unstyled and looks broken. Fix those three and the integration is stable across navigation, hot reloads, and production builds.

Next steps

Once the core editor runs, extend it with blocks and plugins. See the related framework guides for GrapesJS in React and GrapesJS in Vue, browse ready-made GrapesJS plugins and templates, or try the editor directly from the GJS.Market home page.

FAQ

Does GrapesJS work with SvelteKit server-side rendering?

Yes. GrapesJS needs the DOM, so mount it inside onMount, which only runs in the browser. The route around it can still be server-rendered; only the editor instance is client-only.

How do I save GrapesJS content in a Svelte app?

Use the Storage Manager with type: 'remote' to POST project data to your backend, or read editor.getHtml() and editor.getCss() on a save button and send them yourself.

Do I need to destroy the editor on unmount?

Yes. Return editor.destroy() from onMount so GrapesJS removes its listeners and DOM nodes during client-side navigation, preventing memory leaks.

More tags:
Published May 13, 2026
Updated Jun 27, 2026
🔌 GJS.Market

Looking for GrapesJS plugins?

Over 100 curated plugins, presets, and templates — hand-picked for quality and maintained by the community.

Share this postTwitterFacebookLinkedIn
Published via
DevFuture Development
DevFuture Development
Visit shop →

More from DevFuture Development

Discover other insightful posts and stay updated with the latest content.

View all posts

Premium plugins from DevFuture Development

Hand-picked paid additions crafted by this creator.

Visit shop →