Why GrapesJS fits Rails
GrapesJS is a browser-only, MIT-licensed editor, so Rails serves the editor page and exposes load/store endpoints while GrapesJS handles the UI. This guide builds a working setup: an ERB view that hosts the editor, a controller that persists content with CSRF protection, and HTML/CSS export.
1. Render the editor in a view
Your Rails layout already includes csrf_meta_tags. Create
app/views/editor/show.html.erb:
<div id="gjs"><h1>Edit me in GrapesJS</h1></div>
<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet">
<script src="https://unpkg.com/grapesjs"></script>
<%= javascript_include_tag "editor" %>
2. Initialise GrapesJS with remote storage
// app/javascript/editor.js
const csrf = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content');
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-CSRF-Token': csrf },
},
},
},
});
3. Routes and controller
# config/routes.rb
get "editor", to: "editor#show"
get "editor/load", to: "editor#load"
post "editor/save", to: "editor#save"
# app/controllers/editor_controller.rb
class EditorController < ApplicationController
def show; end
def load
page = Page.find_or_create_by(key: "home")
render json: (page.project || {})
end
def save
page = Page.find_or_create_by(key: "home")
page.update!(
project: params.permit!.to_h,
html: params["gjs-html"],
css: params["gjs-css"]
)
render json: { status: "ok" }
end
end
The X-CSRF-Token header satisfies Rails' default forgery
protection, so you keep CSRF on. Store project as a
jsonb column.
4. Render the exported page
<style><%= raw @page.css %></style>
<%= raw @page.html %>
Only use raw for trusted editors; sanitise otherwise.
Production tips
For a solid Rails setup, store the GrapesJS project in a jsonb column so queries and partial updates stay cheap, and keep the rendered html/css in their own columns for fast serving. If you load GrapesJS through importmap-rails rather than a CDN, pin it with bin/importmap pin grapesjs and confirm it resolves in production where the asset host differs. Keep Rails' forgery protection on — the X-CSRF-Token header from csrf_meta_tags satisfies it. And cache the published page (fragment cache or a CDN), invalidating on save, so the editor's render cost never reaches end users.
Prerequisites
You'll need Ruby 3.x and a Rails 7 application. GrapesJS can be loaded from a CDN in an ERB view or pinned with importmap-rails — no Rails-specific gem is needed. The editor runs in the browser and posts to your controllers, so familiarity with views, routes, controllers, and Rails' forgery protection 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.
Storage deep-dive: a custom adapter
A custom storage adapter reads the CSRF token from the meta tag and posts to your routes:
const csrf = document.querySelector('meta[name="csrf-token"]').content;
editor.Storage.add('rails-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-CSRF-Token': csrf },
body: JSON.stringify(data),
});
},
});
// then: storageManager: { type: 'rails-store' }
Store the project in a jsonb column so it re-loads into the editor exactly.
Performance tips
Load GrapesJS only on the editor screen and defer the script. Cache the published page with Rails fragment caching or a CDN and invalidate on save. If you load GrapesJS through importmap-rails, confirm the pin resolves in production where the asset host differs.
Security considerations
Keep forgery protection on — the X-CSRF-Token header from
csrf_meta_tags satisfies it. Add a before_action that
authorises the current user before saving. Only use raw/html_safe
on stored markup when editors are trusted; otherwise sanitise on output. Validate
the payload size.
Troubleshooting common errors
422 Unprocessable Entity usually means the CSRF token is missing or stale. An unstyled canvas means the stylesheet didn't load. A blank editor means the container selector matched nothing. 413 means the project payload exceeds the body limit.
When to use GrapesJS with Rails
GrapesJS fits when your Rails app needs an embedded visual editor your users control — landing pages, emails, a white-label builder — backed by your own database. For inline rich text, a lighter WYSIWYG is enough; for full-page composition with layout, styling, and HTML/CSS export, GrapesJS is the stronger, MIT-licensed, self-hosted choice.
Next steps
See the related GrapesJS + Laravel and GrapesJS + Django guides, browse the GrapesJS marketplace, or start from the GJS.Market home page.
FAQ
How do I save GrapesJS content to a Rails backend?
Configure the Storage Manager with type: 'remote' and point it at
Rails routes. Send the csrf-token meta value as the
X-CSRF-Token header, then store the project JSON on a model.
How do I load GrapesJS without a heavy build step?
Include it from a CDN in an ERB view, or pin it with importmap-rails — both work because GrapesJS is plain browser JavaScript.
Where should I store the project?
Use a jsonb column for the full project, plus optional text columns
for the rendered html and css.