Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Grapesjs Laravel Package

jawad-topdot/laravel-grapesjs

GrapesJS integration for Laravel that adds a visual drag-and-drop page builder to your app. Design HTML templates in the browser, save them to Eloquent models, and render them in Blade. Ships with controllers, routes, migrations, and views for quick setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the package assets/config:

composer require jawad-topdot/laravel-grapesjs
php artisan vendor:publish --provider="JawadTopdot\LaravelGrapesjs\LaravelGrapesjsServiceProvider"
php artisan storage:link

Where to look first, in order:

  1. config/grapesjs.php — this is the control panel. It decides which GrapesJS plugins/presets load, editor height, and the storage/upload wiring. Read this before writing any editor code.
  2. The package's published Blade view/component (under resources/views/vendor/...) — the editor is bootstrapped there; you will almost always copy or override it rather than call grapesjs.init() yourself.
  3. The package routes (php artisan route:list | grep -i grapes) — upload/storage endpoints are usually registered by the service provider. Put them behind auth middleware.
  4. The README's "Usage" snippet — confirm the actual component/partial name and the JS event it expects for saving.

Minimal first use case — a "Page builder" CRUD:

php artisan make:model Page -mcr
// migration
$table->string('title');
$table->longText('html')->nullable();
$table->longText('css')->nullable();
$table->json('project_data')->nullable(); // to re-open the editor later
{{-- resources/views/pages/edit.blade.php --}}
<form method="POST" action="{{ route('pages.update', $page) }}" id="page-form">
    @csrf @method('PUT')
    <input type="hidden" name="html" id="page-html">
    <input type="hidden" name="css"  id="page-css">
    <input type="hidden" name="project_data" id="page-project">

    {{-- the editor component/partial shipped by the package --}}
    <x-grapesjs :height="700" />
</form>

Then wire the submit handler (see next section). The first win is: load the editor, drag a few blocks, hit save, and {!! $page->html !!} renders on a public route. Get that loop working before touching plugins or custom blocks.

Implementation Patterns

1. Save the two representations, not one. Save getHtml() + getCss() for rendering, and getProjectData() (JSON) for editing. HTML/CSS alone loses component structure — users can't cleanly re-edit blocks.

const editor = grapesjs.init({ container: '#gjs', /* config from package */ });

// hydrate on edit
editor.setComponents(@json($page->html ?? ''));
editor.setStyle(@json($page->css ?? ''));

// serialize on submit
document.getElementById('page-form').addEventListener('submit', () => {
    document.getElementById('page-html').value    = editor.getHtml();
    document.getElementById('page-css').value     = editor.getCss();
    document.getElementById('page-project').value = JSON.stringify(editor.getProjectData());
});
// PageController@update
$data = $request->validate([
    'html' => ['nullable', 'string'],
    'css'  => ['nullable', 'string'],
    'project_data' => ['nullable', 'json'],
]);
$page->update($data);

2. Render through a single partial. Never dump html/css in several places. One resources/views/pages/render.blade.php that scopes CSS:

<div class="gjs-content">
    <style>{!! $page->css !!}</style>
    {!! $page->html !!}
</div>

Because GrapesJS emits bare selectors (h1 { ... }, .card { ... }), wrap and prefix: str_replace(['h1','.card'], ['.gjs-content h1','.gjs-content .card'], $css) or, more robustly, render inside a sandboxed iframe route.

3. Configure plugins in config, not in Blade. Day-to-day work happens by editing config/grapesjs.php:

'plugins' => [
    'grapesjs-preset-webpage',
    'grapesjs-blocks-basic',
    'grapesjs-plugin-forms',
    'grapesjs-tabs',
],
'pluginsOpts' => [
    'grapesjs-preset-webpage' => ['modalImportTitle' => 'Import'],
],

Load the matching JS/CSS from npm/CDN consistently — plugin versions must match the bundled GrapesJS version.

4. Storage manager for image uploads. Point GrapesJS at the package's upload route (or your own) so images land in storage/app/public/pages:

const editor = grapesjs.init({
    container: '#gjs',
    storageManager: {
        type: 'remote',
        stepsBeforeSave: 1,
        options: {
            remote: {
                urlStore: '/grapesjs/upload',   // confirm exact route name
                urlLoad: '/grapesjs/assets',
                headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
                fetchOptions: (opts) => ({ ...opts, credentials: 'same-origin' }),
            },
        },
    },
    assetManager: { upload: '/grapesjs/upload', uploadName: 'files[]', autoAdd: true },
});

Store only relative paths in the DB (/storage/pages/abc.png) and run them through Storage::url() at render time so a domain move doesn't break every image.

5. Custom blocks backed by Blade partials. The highest-leverage pattern: expose your app's data to the editor.

editor.BlockManager.add('latest-posts', {
    label: 'Latest posts',
    category: 'Dynamic',
    content: '<div data-dynamic="latest-posts">Loading…</div>',
});

Then a small JS on('load') pass (or a dedicated Blade partial renderer) replaces [data-dynamic] placeholders on the public side, so editors place "components" while the server supplies the data. Keep the placeholder contract stable — it's the seam between editor and app.

6. Revisions for a page builder. Because a stray drag can wreck a layout, add page_revisions and write a snapshot on every save (or on a throttled editor.on('update') debounce). A "Restore" button is a 10-line controller method and saves you support time.

7. Embedding in Livewire/Filament. The editor must not be re-rendered by Livewire. Wrap the container in wire:ignore, initialize GrapesJS once, and push state into Livewire with @this.set('html', editor.getHtml()) on a debounced update event. Otherwise the iframe is destroyed on every render.

8. Auth + authorization first. Editor routes (create, edit, upload) should sit behind auth and a policy ($this->authorize('update', $page)). Public rendering goes through a separate read-only route.

Gotchas and Tips

  • Version drift is the main risk. Last release was May 2023; GrapesJS has moved on. Pin the GrapesJS core version and every preset plugin (grapesjs-preset-webpage, grapesjs-blocks-basic, etc.) in package.json and verify against the version the package's view loads. Plugin/preset mismatches are the #1 source of "editor loads but blocks are blank" reports.
  • CSRF on storage manager. If uploads 419, it is almost always a missing X-CSRF-TOKEN header or a fetch without credentials: 'same-origin'. Add a <meta name="csrf-token"> and pass it via headers.
  • The canvas is an iframe. Console errors from custom block scripts appear in the iframe context, not the parent — open devtools and switch frame context. To talk to the parent app, use editor.on('load') plus postMessage, or drive the host via editor.Commands.
  • CSS leakage. getCss() produces global selectors. Without scoping or an iframe render, your saved page CSS will rewrite the surrounding app's typography. Always render inside a wrapper class (prefix selectors) or a sandboxed iframe.
  • XSS by design. GrapesJS saves raw HTML you later render with {!! !!}. Either restrict editing to trusted admins, or pass content through HTMLPurifier (mews/purifier) before render. Do not skip this if editors are end users.
  • getHtml() vs getProjectData(). HTML/CSS is for output; project JSON is for editing. Saving only HTML means "re-open in editor" starts from flat markup and blocks break.
  • Absolute URLs. GrapesJS can bake http://localhost:8000/storage/... into saved content. Normalize on save (str_replace(config('app.url'), '', $html)) and resolve at render.
  • Config cache and view cache. After editing config/grapesjs.php, run php artisan config:clear; after overriding the package view, php artisan view:clear. A stale published view is a common "my change did nothing" cause.
  • Route collisions. The package registers upload/storage routes. Run php artisan route:list before adding your own /grapesjs/* routes, and if you need custom behavior, disable/override the package route in config/grapesjs.php rather than shadowing it.
  • Middleware on package routes. Published package routes often lack auth. Wrap the upload endpoint behind ['web', 'auth'] in config or re-register it in your own routes/web.php.
  • Unsaved-changes dialog. Hook beforeunload when editor.getDirtyCount() > 0. Editors lose work on accidental back-navigation otherwise.
  • storage:link and APP_URL. Broken images in the asset manager are usually an unlinked storage dir or an APP_URL that doesn't match where you serve. Check Storage::url() output first.
  • CSP/offline. If the package pulls GrapesJS from a CDN, a strict Content-Security-Policy or an air-gapped deploy breaks it. Copy the dist files into public/vendor/grapesjs and reference them locally.
  • Extension point. For anything the config doesn't expose, publish/override the editor Blade view — that file is the intended extension seam. Don't fork the package for block config; add blocks from your own @push('scripts') after grapesjs.init.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core