How to build a GrapesJS desktop app with Electron (complete guide 2026)

Run GrapesJS inside an Electron desktop app: mount it in the renderer, save projects to disk through IPC, and export HTML/CSS.

DevFuture Development
DevFuture Development
Jun 10, 202618 days ago
7 min read1 views

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

Colourful code on a dark screen
Keep contextIsolation on and expose a tiny, explicit API.

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.

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