Why GrapesJS fits Django
GrapesJS is a browser-only, MIT-licensed editor, so Django's role is simply to serve the editor template and expose two endpoints — one to load the saved project and one to store it. This guide builds a working setup: a template that hosts the editor, a view that persists content with CSRF protection, and HTML/CSS export.
1. Render the editor in a template
Create templates/editor.html. Django sets a csrftoken
cookie automatically; we read it in JavaScript for the save request.
{% load static %}
<!doctype html>
<html>
<head>
<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet">
</head>
<body>
<div id="gjs"><h1>Edit me in GrapesJS</h1></div>
<script src="https://unpkg.com/grapesjs"></script>
<script src="{% static 'js/editor.js' %}"></script>
</body>
</html>
2. Initialise GrapesJS with remote storage
In static/js/editor.js, read the CSRF cookie and configure the
Storage Manager to POST to Django.
function getCookie(name) {
const match = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
return match ? match.pop() : '';
}
const editor = grapesjs.init({
container: '#gjs',
height: '100vh',
fromElement: true,
storageManager: {
type: 'remote',
stepsBeforeSave: 3,
options: {
remote: {
urlStore: '/editor/save/',
urlLoad: '/editor/load/',
headers: { 'X-CSRFToken': getCookie('csrftoken') },
},
},
},
});
3. Add the model, views, and URLs
Store the project as JSON on a model:
# models.py
from django.db import models
class Page(models.Model):
key = models.SlugField(unique=True, default="home")
project = models.JSONField(default=dict) # full editable project
html = models.TextField(blank=True)
css = models.TextField(blank=True)
# views.py
import json
from django.http import JsonResponse
from django.shortcuts import render
from .models import Page
def editor(request):
return render(request, "editor.html")
def load(request):
page, _ = Page.objects.get_or_create(key="home")
return JsonResponse(page.project, safe=False)
def save(request):
data = json.loads(request.body or "{}")
Page.objects.update_or_create(
key="home",
defaults={
"project": data,
"html": data.get("gjs-html", ""),
"css": data.get("gjs-css", ""),
},
)
return JsonResponse({"status": "ok"})
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("editor/", views.editor),
path("editor/load/", views.load),
path("editor/save/", views.save),
]
Because the request carries the X-CSRFToken header,
CsrfViewMiddleware accepts the POST without you disabling CSRF.
4. Render the exported page
<style>{{ page.css|safe }}</style>
{{ page.html|safe }}
Only mark stored markup |safe when your editors are trusted;
sanitise on output otherwise.
Common pitfalls in Django
Two issues trip up most Django integrations. The first is CSRF: if you set CSRF_COOKIE_HTTPONLY = True, JavaScript can't read the csrftoken cookie, so the X-CSRFToken header is empty and the POST is rejected — keep it readable, or render the token into the template. The second is storage type: use a JSONField for the full project, not a TextField you parse by hand, so the data re-loads into the editor exactly. In production, the editor's static editor.js must also be collected by collectstatic and served (WhiteNoise or a CDN), or the page loads GrapesJS but never initialises it.
Prerequisites
You'll need Python 3.10+ and a Django 4 or 5 project. Node.js is optional — load GrapesJS from a CDN in a template, or bundle it with your asset toolchain. There is no Django-specific GrapesJS package; the editor runs in the browser and talks to your views over HTTP, so familiarity with templates, views, URLs, and Django's CSRF cookie is all you need.
Add custom blocks to the editor
Register draggable building 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>',
});
Reach for ready-made block libraries and presets from GJS.Market for a richer starting set.
Storage deep-dive: a custom adapter
The remote preset is the quickest path, but a custom storage adapter gives you full control over how the project is loaded and saved — handy for optimistic UI or non-standard stores:
function getCookie(name) {
const m = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
return m ? m.pop() : '';
}
editor.Storage.add('django-store', {
async load() {
const res = await fetch('/editor/load/');
return res.ok ? res.json() : {};
},
async store(data) {
await fetch('/editor/save/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') },
body: JSON.stringify(data),
});
},
});
// then: storageManager: { type: 'django-store' }
Store the project in a JSONField so it re-loads into the editor exactly.
Performance tips
Serve the editor's JavaScript only on the editor page and defer it so it never
blocks first paint. Cache the published page (Django's cache framework or a CDN)
and invalidate on save. In production the editor's static files must be collected
by collectstatic and served by WhiteNoise or a CDN, or the page loads
GrapesJS but never initialises.
Security considerations
Keep CSRF protection on and send the token in the X-CSRFToken header.
Protect the save view with login_required and a permission check so
only authorised users can overwrite a page. Mark stored markup |safe
only when editors are trusted — otherwise sanitise it on output. Cap the request
body size so an oversized project can't exhaust memory.
Troubleshooting common errors
403 Forbidden (CSRF) means the csrftoken cookie is
unreadable (often CSRF_COOKIE_HTTPONLY = True) or the header is
missing. An unstyled canvas means the GrapesJS stylesheet didn't
load. A blank editor means the container selector matched nothing.
413 means the project payload exceeds your server's body limit.
When to use GrapesJS with Django
GrapesJS fits when your Django app needs an embedded visual page or email builder your users control, backed by your own models and served from your infrastructure. For inline rich text in a single field, a lighter WYSIWYG is enough; for full-page composition with layout, styling, and clean HTML/CSS export, GrapesJS is the stronger, MIT-licensed, self-hosted choice.
Next steps
Extend the editor with blocks and plugins. See the related GrapesJS + React guide, browse storage adapter plugins and the full GrapesJS marketplace, or start from the GJS.Market home page.
FAQ
How do I save GrapesJS content to a Django backend?
Configure the Storage Manager with type: 'remote' and point
urlStore/urlLoad at Django URLs. Send the CSRF token in
the X-CSRFToken header, then store the project JSON in a model field.
Where should I store the project data?
Use a JSONField for the full project so it re-loads into the editor
exactly, plus optional html/css text fields for publishing.
How do I send the CSRF token from GrapesJS to Django?
Read the csrftoken cookie in JavaScript and pass it as the
X-CSRFToken header in the Storage Manager's headers option.