Why GrapesJS fits Electron
Electron renders web pages in a desktop window, and GrapesJS is a browser editor, so it runs in the renderer with no changes. The desktop twist is storage: instead of a server, you save projects to disk through Electron's IPC. This guide mounts the editor, saves to disk securely, and exports HTML/CSS — fully offline.
1. Expose a safe save/load API from preload
Keep contextIsolation on and bridge only what you need in
preload.js:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('store', {
load: () => ipcRenderer.invoke('page:load'),
save: (data) => ipcRenderer.invoke('page:save', data),
});
2. Handle disk I/O in the main process
const { app, ipcMain } = require('electron');
const fs = require('fs/promises');
const path = require('path');
const file = path.join(app.getPath('userData'), 'page.json');
ipcMain.handle('page:load', async () => {
try { return JSON.parse(await fs.readFile(file, 'utf8')); }
catch { return {}; }
});
ipcMain.handle('page:save', async (_e, data) => {
await fs.writeFile(file, JSON.stringify(data));
return { status: 'ok' };
});
3. Mount GrapesJS in the renderer
import grapesjs from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
const editor = grapesjs.init({
container: '#gjs',
height: '100vh',
fromElement: true,
storageManager: false,
});
// Load saved project on start, save on demand.
window.store.load().then((p) => p.project && editor.loadProjectData(p.project));
document.getElementById('save').onclick = () =>
window.store.save({
project: editor.getProjectData(),
html: editor.getHtml(),
css: editor.getCss(),
});
Security tips
A desktop editor still deserves a tight security posture. Keep contextIsolation: true and sandbox: true, and never enable nodeIntegration in the renderer — expose only a minimal load/save API through contextBridge in a preload script. Validate the payload in the main process before writing it to disk (size, shape), since the renderer loads remote GrapesJS code. Write under app.getPath('userData') so files land in the OS-appropriate location, and consider atomic writes (temp file + rename) so a crash mid-save never corrupts the project.
Prerequisites
You'll need Node.js 18+ and Electron. The editor runs in the renderer like any web page; the desktop twist is storage — you save to disk through Electron's IPC instead of an HTTP server. Familiarity with the main/renderer split, preload scripts, and IPC is enough.
Add custom blocks to the editor
Register draggable blocks with the Block Manager after grapesjs.init in the renderer:
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.
Atomic writes on disk
Write the project to a temp file and rename it into place so a crash mid-save never corrupts the file:
ipcMain.handle('page:save', async (_e, data) => {
const tmp = file + '.tmp';
await fs.writeFile(tmp, JSON.stringify(data));
await fs.rename(tmp, file); // atomic on most filesystems
return { status: 'ok' };
});
Performance tips
Bundle GrapesJS with your renderer (via Vite or Webpack) so it loads from local files and the app works fully offline. Lazy-load heavy plugins only when their feature is opened. Destroy the editor when the view unmounts so navigating between windows or routes doesn't leak instances.
Security considerations
Keep contextIsolation: true and sandbox: true, and never
enable nodeIntegration in the renderer — expose only a minimal
load/save API through contextBridge. Validate
the payload (shape and size) in the main process before writing, since the renderer
loads remote editor code. Write under app.getPath('userData').
Troubleshooting common errors
“require is not defined” in the renderer means you tried to use Node APIs directly — go through the preload bridge instead. An unstyled or blank canvas means the stylesheet didn't load or the container wasn't present. Saves silently fail usually means the IPC channel name or handler doesn't match.
When to use GrapesJS in Electron
GrapesJS fits a desktop app that needs an offline visual editor — a local page or email designer, a documentation builder, a kiosk content tool. Because it's MIT-licensed and self-contained, you ship the whole editing experience inside your app with no server and no per-seat fees.
Next steps
See the related GrapesJS + Vite setup (handy for bundling the renderer) and GrapesJS + React guides, browse the GrapesJS marketplace, or start from the GJS.Market home page.
FAQ
Can GrapesJS run offline in Electron?
Yes — bundle GrapesJS with the renderer and store projects on disk via the main process. No network required.
How do I save projects to disk?
Send the project from the renderer with ipcRenderer.invoke (exposed
via preload) and write it with fs in the main process, under
app.getPath('userData').
Should I enable contextIsolation?
Yes — keep it on and expose a small API with contextBridge instead
of enabling nodeIntegration.