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

Laraberg Laravel Package

van-ons/laraberg

Laraberg integrates the WordPress Gutenberg block editor into Laravel, giving your apps a modern WYSIWYG editor with blocks, media uploads, and easy content rendering.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require van-ons/laraberg:^2.0
php artisan vendor:publish --provider="VanOns\Laraberg\LarabergServiceProvider"
php artisan migrate

Publish puts its config (config/laraberg.php), migrations, and compiled editor assets (JS/CSS) into public/vendor/laraberg. If you only want part of it, use a tag (laraberg, laraberg-config, laraberg-migrations).

The package is now on the 2.x line (current: v2.0.4). If you are following older tutorials or had pinned a 0.8.* release, treat 2.0 as a major jump: re-read the config file your install publishes rather than assuming keys from a pre-1.0 guide still exist, and re-publish assets after upgrading.

Where to look first

  1. config/laraberg.php — asset paths, upload disk, rendering options (verify key names against your installed version, they have moved across majors).
  2. vendor/van-ons/laraberg/src/ — the Laraberg facade class, Traits/, and the PHP-side block renderers.
  3. resources/js inside the package (if vendored) — only relevant if you need to rebuild or extend blocks; the shipped laraberg.js bundles Gutenberg/React and is normally used as-is.

Minimal first use case: a rich body field on a Post

Your model keeps raw Gutenberg HTML in a longText column:

Schema::table('posts', function (Blueprint $table) {
    $table->longText('content')->nullable();
});

In the editor form (Blade):

<link rel="stylesheet" href="{{ asset('vendor/laraberg/css/laraberg.css') }}">
<script src="{{ asset('vendor/laraberg/js/laraberg.js') }}"></script>

<form method="POST" action="{{ route('posts.store') }}">
    @csrf
    <textarea id="content" name="content">{{ old('content', $post->content ?? '') }}</textarea>
    <button type="submit">Save</button>
</form>

<script>
    document.addEventListener('DOMContentLoaded', function () {
        Laraberg.init('content', { height: '600px' });
    });
</script>

Laraberg.init(id) hides the <textarea> and mounts Gutenberg on top of it; on save it serializes blocks back into the textarea value as HTML containing block comments. Store that string verbatim — it is the source of truth.

Rendering on the front end

use VanOns\Laraberg\Laraberg;

{!! Laraberg::render($post->content) !!}

The package also ships a RendersContent trait for models so you can do $post->renderContent() (check src/Traits in your installed version for the exact signature). Whichever route you take, this is the single most important concept: you persist block markup, and Laraberg transforms it to clean HTML on read.

Implementation Patterns

Store raw, render on read (or cache rendered) Keep the block markup in content and render at output time. If rendering is expensive or you serve high traffic, add a content_rendered column and refresh it in an observer:

class PostObserver
{
    public function saving(Post $post): void
    {
        $post->content_rendered = Laraberg::render($post->content);
    }
}

This also makes full-text search and OG/excerpt generation possible without parsing blocks on every request.

Controller + Form Request Treat the field as trusted HTML only if you trust the author. Otherwise sanitize:

class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['nullable', 'string'],
        ];
    }

    protected function prepareForValidation(): void
    {
        // Optional: strip script/style/iframe while preserving Gutenberg comments
        if ($this->has('content')) {
            $this->merge(['content' => clean($this->input('content'))]);
        }
    }
}

Careful: an overly aggressive cleaner will eat the <!-- wp:paragraph --> comments and break re-editing. Test round-trips.

Two-column admin layout with a sidebar Gutenberg is a full-screen-ish editor. Give it a wrapper with a fixed height, and use the sidebar option if you want your own settings panels (status, featured image, SEO fields) rendered by Gutenberg's React tree rather than duplicating a second panel in Blade.

Media uploads: implement fetchHandler The stock Gutenberg media library expects a WordPress-shaped response. Wire fetchHandler to a Laravel endpoint and return an attachment-like payload:

Laraberg.init('content', {
    height: '600px',
    fetchHandler: async (url, options) => {
        const response = await fetch(url, {
            ...options,
            headers: {
                ...options.headers,
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
                'Accept': 'application/json',
            },
        });
        return response.json();
    },
});
// MediaController@store
$path = $request->file('file')->store('uploads', 'public');

return response()->json([
    'id'         => $media->id,
    'url'        => Storage::disk('public')->url($path),
    'source_url' => Storage::disk('public')->url($path),
    'title'      => $media->name,
    'mime'       => $request->file('file')->getMimeType(),
    'type'       => 'image',
]);

Run php artisan storage:link or the URLs will 404 inside the editor canvas.

Laravel Filemanager If you already run unisharp/laravel-filemanager, the package has a built-in bridge (laravelFilemanager option with a prefix), which saves you writing the media endpoint at all. For most CRUD apps this is the fastest path to a working image block.

Locking down the block set disabledBlocks (e.g. ['core/embed', 'core/html']) is the cheapest way to keep output predictable and reduce XSS surface — core/html lets an author paste arbitrary markup.

Re-initialization with Livewire/Inertia/Turbo Because Laraberg.init mutates the DOM, hook it into your navigation lifecycle (Livewire.hook('morph.updated', ...), Inertia.on('navigate', ...), or turbo:load) rather than only DOMContentLoaded, or the editor silently fails to mount after client-side navigation.

Gotchas and Tips

License: GPL-3.0. This is the biggest non-technical consideration. Laraberg is copyleft; distributing a proprietary product that bundles it can obligate you to release source under GPL terms. Internal tools and SaaS (non-AGPL) are generally lower risk, but get a legal read before shipping a commercial desktop/on-prem build. Also note the JS bundle embeds WordPress Gutenberg code, which is GPL-derived as well.

Maintenance risk. The project is no longer dormant on the 0.x line — 2.x releases are shipping, with v2.0.4 landing a targeted bugfix. That is a good sign, but it is still a small-maintainer package tracking Gutenberg, which moves fast, so block-level bugs and WordPress parity drift can sit unfixed. Before committing: verify compatibility against your Laravel/PHP version, and plan to either follow the 2.x line closely or fork.

Upgrading a 0.x install to 2.x. This is a major version boundary and the migration cost is real: config keys, asset public paths, and the init options surface have changed enough across the majors that copy-pasted snippets from older tutorials will fail in confusing ways (usually a silent no-op rather than an error). Publish the new config side-by-side with your old one, diff it, re-publish assets with --force, and smoke-test the editor and render() output before touching production content.

v2.0.4 changed media fetch behaviour. The release adds a guard so the editor no longer attempts to fetch when the configured media endpoint cannot be found. Practically: a misconfigured or unpublished media route now degrades more quietly. If image uploads or the media library "do nothing" after upgrading, check the endpoint URL in your fetchHandler/laravelFilemanager config first — you may no longer get the noisy failed request that used to point you at the problem. Log 404s server-side and confirm the route exists (php artisan route:list).

Version pinning + lockfile hygiene. Because the JS bundle ships as compiled assets out of vendor/, an unrelated composer update can swap editor behavior with no diff in your repo. Pin the version ("van-ons/laraberg": "^2.0" or tighter) and re-run vendor:publish --force --tag=laraberg after every dependency bump, then smoke-test the editor.

Asset pipeline collisions. The shipped laraberg.js bundles React and registers wp.* globals. Importing it into a Vite bundle alongside your own React (or another package that touches window.wp) produces "two Reacts" symptoms: hooks errors, blank canvases, stale state. Load it as a classic <script> tag before your app bundle, and check window.Laraberg in the console.

Escaping and XSS. You must render with {!! !!}, which means any author can inject markup. Gutenberg's own sanitizer only runs in the browser — never on your server. Sanitize server-side on save (HTMLPurifier/mews/purifier) and restrict core/html if posts come from non-admin users.

Block comments get stripped by cleaners. Any sanitizer that strips HTML comments will destroy the <!-- wp:* --> boundaries. You'll notice this when re-opening a post: everything collapses into a single classic/freeform block. Whitelist comment syntax or sanitize only at render time.

Textarea id vs. name. Laraberg.init('content') takes the element id; the form field needs the matching name. Mismatches are the #1 "it saved nothing" cause — the textarea is display:none and easy to forget.

Admin CSS bleed. Gutenberg injects a large stylesheet; it can restyle your surrounding layout (button, input, focus rings, body resets). Scope the editor inside your admin shell and be prepared to override a handful of selectors.

Large content in MySQL. longText is fine, but rendering per request is not free. Cache rendered HTML in a column or cache key, and remember LIKE '%...%' searches over block markup are noisy — use the rendered column for search indexing.

Debugging checklist

  • 404 on vendor/laraberg/js/laraberg.js → publish again, confirm public/vendor/laraberg exists and is gitignored correctly.
  • Blank grey area → JS error before init; check console, confirm Laraberg is defined.
  • Images upload but don't display → storage:link missing or wrong disk in config.
  • Media library silently empty/non-responsive (2.0.4+) → endpoint guard is swallowing a missing/renamed route; verify the URL and check your server access log for 404s.
  • Content re-opens as one giant block → sanitizer or editor stripped block comments.
  • Save works once, then breaks → double init on the same id (common with Livewire morphing).

Extension points worth knowing. The PHP side is where the leverage is: subclass or register custom block renderers to control output for your own blocks, and wrap Laraberg::render in your own service so you can add caching, sanitization, and shortcode-style transformations in one place rather than scattering {!! !!} calls through Blade.

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.
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent