How to integrate GrapesJS with an Express / Node.js backend (complete guide 2026)

Serve the GrapesJS editor from an Express app and persist content with a Node.js backend: load/store routes, the Storage Manager, and HTML/CSS export.

DevFuture Development
DevFuture Development
Jun 6, 202622 days ago
6 min read4 views

Why GrapesJS fits Express / Node

GrapesJS runs in the browser, so Express simply serves the editor page and two JSON routes — one to load the saved project, one to store it. This guide serves the editor, persists content with Node, and exports HTML/CSS.

1. The Express server

import express from 'express';
const app = express();
app.use(express.json());
app.use(express.static('public'));   // serves public/index.html

let page = {};                       // swap for a real DB

app.get('/load', (req, res) => res.json(page));

app.post('/save', (req, res) => {
  page = req.body;                   // full GrapesJS project
  res.json({ status: 'ok' });
});

app.listen(3000, () => console.log('http://localhost:3000'));

2. The editor page

Create public/index.html:

<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet">
<div id="gjs"><h1>Edit me</h1></div>
<script src="https://unpkg.com/grapesjs"></script>
<script>
  grapesjs.init({
    container: '#gjs',
    height: '100vh',
    fromElement: true,
    storageManager: {
      type: 'remote',
      stepsBeforeSave: 3,
      options: { remote: { urlStore: '/save', urlLoad: '/load' } },
    },
  });
</script>

Because the page and the API share the same Express origin, requests are same-origin and you avoid CORS configuration entirely.

3. Persist to a real database

app.post('/save', async (req, res) => {
  await db.pages.updateOne(
    { key: 'home' },
    { $set: { project: req.body, html: req.body['gjs-html'], css: req.body['gjs-css'] } },
    { upsert: true }
  );
  res.json({ status: 'ok' });
});

Production tips

Server-side code on a laptop
Raise the JSON body limit and persist to a real store.

A prototype that stores the page in a variable won't survive a restart — persist to a database (a JSON/JSONB column or a Mongo document) keyed by page. GrapesJS projects can be large, so raise Express's body limit (express.json({ limit: '5mb' })) or saves silently fail with 413. Protect the store route with authentication so anyone can't overwrite a page, and serve the editor and the API from the same Express origin to avoid CORS entirely. If they must differ, enable CORS for the editor origin and allow the Content-Type header.

Prerequisites

You'll need Node.js 18+ and Express 4 or 5. GrapesJS runs in the browser, so Express serves the editor page and two JSON routes — load and store. Familiarity with middleware, routing, and JSON body parsing is enough.

Add custom blocks to the editor

Register draggable blocks with the Block Manager after grapesjs.init:

editor.BlockManager.add('hero', {
  label: 'Hero section',
  category: 'Sections',
  content: '<section class="hero"><h1>Headline</h1><p>Copy</p></section>',
});

Pull ready-made block libraries and presets from GJS.Market for a richer set.

Persist to a real database

A prototype that stores the page in a variable won't survive a restart. Persist to a database keyed by page:

app.post('/save', async (req, res) => {
  await db.pages.updateOne(
    { key: 'home' },
    { $set: { project: req.body, html: req.body['gjs-html'], css: req.body['gjs-css'] } },
    { upsert: true }
  );
  res.json({ status: 'ok' });
});

app.get('/load', async (req, res) => {
  const page = await db.pages.findOne({ key: 'home' });
  res.json(page?.project ?? {});
});

Performance tips

GrapesJS projects can be large, so raise Express's body limit (express.json({ limit: '5mb' })) or saves fail with 413. Serve the editor and API from the same origin to avoid CORS. Cache the rendered output and bust it on save so visitors never pay the render cost.

Security considerations

Protect the store route with authentication so anyone can't overwrite a page. If the editor and API live on different origins, enable CORS only for the editor origin and allow the Content-Type header. Sanitise stored markup on output if non-admins can edit. Validate the payload size.

Troubleshooting common errors

413 Payload Too Large means the JSON exceeds the body limit — raise it. A CORS error means the editor and API are on different origins without CORS configured. An unstyled or blank canvas means the stylesheet didn't load or the container wasn't present.

When to use GrapesJS with Express / Node

GrapesJS fits a custom Node app that needs an embedded visual editor with your own storage — a SaaS page builder, an email composer, a CMS surface. For inline rich text, a lighter editor is enough; for full-page composition with layout, styling, and clean export, GrapesJS is the stronger, MIT-licensed choice.

Next steps

See the related GrapesJS + React and GrapesJS + Laravel guides, browse storage adapter plugins and the GrapesJS marketplace, or start from the GJS.Market home page.

FAQ

How does GrapesJS talk to an Express backend?

Set the Storage Manager to type: 'remote' with urlStore/urlLoad pointing at Express routes; add express.json() and read req.body.

Where should Node store the project?

A JSON column, a MongoDB document, or a file for a prototype — store the full project plus optional html/css.

How do I avoid CORS issues?

Serve the editor and API from the same Express origin so requests are same-origin; otherwise enable CORS for the editor origin.

More tags:
Published Jun 6, 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

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 →