jeffreyvr/dropblockeditor
Dropblockeditor is a customizable block-based editor for Laravel apps, featuring drag-and-drop content blocks, image uploads, and JSON storage. It integrates with Blade and Tailwind CSS, making flexible page building and rich content editing simple without a complex CMS.
jeffreyvr/dropblockeditor is a Laravel block editor (Livewire + Alpine based) for authoring JSON-structured content and rendering it back through Blade views. Version 0.4.0 is the Laravel 11 compatibility release — no new end-user features — so install it as composer require jeffreyvr/dropblockeditor:^0.4 on Laravel 11 (or ^0.3 on Laravel 10).
Minimal path to a working editor:
config/dropblockeditor.php for asset/route settings:
php artisan vendor:publish --provider="Jeffreyvr\Dropblockeditor\DropblockeditorServiceProvider"
json (or longtext) content column and cast it in the model: protected $casts = ['content' => 'array'];.text block.Editor class (extends the package's Editor) that returns the block classes available for that resource.Where to look first: the package src/ directory (Editor, Blocks/Block, the Livewire component), resources/views for the editor shell, the config file, and any stub block shipped with the publish command. Those four define everything you will touch.
First use case: a "Page" resource with a text block and a heading block, edited in an admin form and rendered on the frontend. Get that end-to-end before adding media or nested blocks.
One block class per content type. Keep the editing form view and the frontend render view as separate files (e.g. blocks.text.form / blocks.text.render). The form view is admin-only and can use Livewire bindings; the render view must be plain, server-side Blade with no Livewire dependency.
Per-resource Editor classes. Rather than one global editor, define PageEditor, PostEditor, etc., each returning its own block list. That keeps the block picker tight and stops content authors from pasting a block into a resource whose renderer doesn't handle it.
Store JSON, render by type. Cast the column to array, then render with a mapping loop and a whitelist:
@foreach ($page->content ?? [] as $block)
@switch($block['type'])
@case('text')
<x-blocks.text :data="$block" />
@break
@case('heading')
<x-blocks.heading :data="$block" />
@break
@endswitch
@endforeach
Never @include a view name taken straight from the stored JSON — that is a template-injection vector.
Wrap rendering in a Blade component. A <x-block-content :content="$model->content" /> component hides the switch statement and gives you one place to add block types, fallbacks for unknown types, and empty-state handling.
Livewire form integration. Wrap the editor in your normal Livewire CRUD form and treat its content property like any other field: validate on save(), then persist. If you use a plain Blade form (no Livewire), post the JSON through a hidden input and json_decode server-side.
Nested/child blocks. If your block form supports children, store them as a nested children array and make the render partial recursive. Keep depth capped in validation — recursive rendering of user-supplied JSON is a denial-of-service risk otherwise.
Media and relations. For image/file blocks, persist the storage disk path or model ID in the block data, not a rendered URL, so you can swap disks/CDNs later. Resolve URLs at render time.
0.x semver means breaking changes ship in minor bumps. There is nothing to migrate from 0.3.1 to 0.4.0 beyond the Laravel 11 support, but pin ^0.4 and read the changelog before every upgrade. Staying on Laravel 10? There is no reason to upgrade yet.
Livewire version alignment is the most common install failure. Laravel 11 implies Livewire 3; an old Livewire 2 install will throw JS errors and broken block interactions. Check the package's composer.json constraint against your livewire/livewire version before debugging anything else.
Re-publish assets after upgrading. After composer update, run php artisan vendor:publish --force (watch that --force doesn't overwrite your customised views — publish to a backup first) and then php artisan view:clear. Stale published JS against new PHP is a classic silent breakage.
Null content is the most frequent runtime error. Pass [] (not null) into the editor and the renderer. Cast with a default: $page->content ?? [].
CSS collisions in the admin. The editor styles can fight your app's Tailwind build. Keep the published editor CSS scoped to the admin layout rather than the global stack, and avoid double-loading Alpine (Livewire 3 already ships it) — double Alpine causes blocks that render but never respond.
Sanitise block HTML. Rich-text blocks store markup. Escape by default, and if you must output raw HTML, run it through a purifier. Validation rules on the block class protect the editor, not your frontend.
Preview endpoints need auth. If you add a preview route (a common pattern: render unsaved JSON via a controller), put it behind your admin middleware — it's an arbitrary-block-render endpoint otherwise.
Testing. Don't try to drive the editor UI in tests. Assert two things: the block class validation rules, and the render output for a fixture JSON array. Blade component tests ($this->blade('<x-block-content :content="$json" />')) cover the risky half cheaply.
Extension points. Customise by subclassing Editor, adding block classes, and overriding published views — not by editing vendor/. If the package doesn't expose a hook you need, prefer wrapping it in your own Blade component over forking; at 0.x, a fork will drift fast.
How can I help you explore Laravel packages today?