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
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.