Why GrapesJS fits Astro
Astro ships zero JavaScript by default and lets you opt into client-side code per component. GrapesJS is a browser-only editor, so you load it in a client script and save through an Astro API endpoint. This guide mounts the editor, persists content, and exports HTML/CSS.
1. Mount the editor in a client script
Create src/pages/editor.astro. The <script> runs
in the browser, so importing GrapesJS there is safe.
---
// src/pages/editor.astro
---
<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet" />
<div id="gjs"><h1>Edit me in GrapesJS</h1></div>
<script>
import grapesjs from 'grapesjs';
const editor = grapesjs.init({
container: '#gjs',
height: '100vh',
fromElement: true,
storageManager: {
type: 'remote',
stepsBeforeSave: 3,
options: { remote: { urlStore: '/api/page', urlLoad: '/api/page' } },
},
});
</script>
2. Add an Astro API endpoint
Create src/pages/api/page.ts. SSR endpoints export HTTP-method
functions.
import type { APIRoute } from 'astro';
import { loadPage, savePage } from '../../lib/store';
export const GET: APIRoute = async () => {
return new Response(JSON.stringify(await loadPage('home')), {
headers: { 'Content-Type': 'application/json' },
});
};
export const POST: APIRoute = async ({ request }) => {
const data = await request.json();
await savePage('home', data); // your DB / KV write
return new Response(JSON.stringify({ status: 'ok' }));
};
Enable an SSR adapter (output: 'server' or 'hybrid' in
astro.config.mjs) so the POST is handled at request time.
3. Export the final markup
const html = editor.getHtml();
const css = editor.getCss();
// Persist or render: `<style>${css}</style>${html}`
Common pitfalls in Astro
Astro's zero-JS default is the thing to remember. The editor must run client-side β a plain <script> tag or a framework island with client:only; importing GrapesJS in frontmatter (server context) will fail. Saving needs runtime: a fully static build can't accept a POST, so set output: 'server' or 'hybrid' with an SSR adapter before wiring the Storage Manager to /api/page. And keep the editor route out of any prerender list β you want it rendered on demand, not frozen at build time.
Prerequisites
You'll need Node.js 18+ and an Astro 4 project. To accept a save at runtime you'll
also want an SSR adapter (output: 'server' or 'hybrid').
No Astro-specific GrapesJS package exists β the editor runs in the browser via a
client script or a framework island, and Astro endpoints handle persistence.
Add custom blocks to the editor
Register draggable blocks with the Block Manager after grapesjs.init inside your client script:
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.
Storage deep-dive: a custom adapter
A custom storage adapter posts the project to your Astro API endpoint:
editor.Storage.add('astro-store', {
async load() {
const res = await fetch('/api/page');
return res.ok ? res.json() : {};
},
async store(data) {
await fetch('/api/page', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
},
});
// then: storageManager: { type: 'astro-store' }
Keep the editor route out of any prerender list so it's rendered on demand, not frozen at build time.
Performance tips
Astro ships zero JS by default, so the editor's weight is opt-in β load it only on
the editor route via a client script or client:only island. Code-split
heavy plugins. Because the rest of the site stays static, your public pages remain
fast even with a heavy editor behind an admin route.
Security considerations
Authenticate and authorise the POST endpoint before writing β a static-feeling site still needs a guarded save route. Sanitise stored markup on output if non-admins can edit. Validate the payload size so a large project can't exhaust memory.
Troubleshooting common errors
An import error in frontmatter means you imported GrapesJS in the
server context β keep it in a client <script> or a
client:only island. The POST 404s or fails usually
means there's no SSR adapter; switch to output: 'server' or
'hybrid'. An unstyled or blank canvas means the
stylesheet didn't load or the container wasn't present.
When to use GrapesJS with Astro
GrapesJS fits when an otherwise static Astro site needs an embedded visual editor behind an admin route β a page builder or newsletter composer β with your own storage and HTML output. For purely static content edited in markdown, Astro's content collections are simpler; for visual full-page composition, GrapesJS is the stronger, MIT-licensed choice.
Next steps
See the related GrapesJS + Svelte and GrapesJS + React guides, browse the GrapesJS marketplace, or start from the GJS.Market home page.
FAQ
Does GrapesJS work in an Astro island?
Yes β use a client-side <script> or a framework island with
client:only so GrapesJS runs in the browser.
How do I save GrapesJS content in Astro?
Add an API endpoint in src/pages/api exporting POST and
have the Storage Manager post the project JSON to it.
Do I need an adapter for the save endpoint?
Yes β handling POST at runtime needs an SSR adapter
(output: 'server' or 'hybrid'); a fully static build
can't accept writes.