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

Filament Page Builder Laravel Package

carljanzell/filament-page-builder

A page builder for Filament that lets you assemble pages from reusable blocks. Define custom block types, drag and drop them into flexible layouts, and edit content straight from the admin panel. A practical way to give editors structured control over Laravel site pages.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this as a Filament form field plus a JSON storage convention, not a full CMS. The package gives editors a drag-and-drop block editor; you still own the block library, rendering, routing, validation, and caching.

  1. Confirm the compatible branch before installing. Filament 3 uses the v0.3.1 line. Filament 4.12.6+ and 5.x use v0.4.x and require PHP 8.3+. Pin an exact version in composer.json; this is a low-adoption package (14 stars, 0 dependents), so avoid floating constraints.
  2. Install and register the plugin in your panel provider, following the package README for the exact plugin class name. If the package publishes config, assets, or migrations, publish them before first use.
  3. Add a JSON column to the model that represents a page, landing page, or content record:
$table->json('content')->nullable();

Cast it on the model:

protected $casts = [
    'content' => 'array',
];
  1. Add the builder field to a Filament resource form. The package exposes a Filament field class; search src/ for the class implementing Filament\Forms\Components\Field if the README is thin. Configure the allowed block types there rather than letting editors insert arbitrary types.
  2. Render the first page in Blade by looping over the ordered blocks and dispatching to a partial per type:
@foreach ($page->content ?? [] as $block)
    @includeIf("page-builder.blocks.{$block['type']}", [
        'data' => $block['data'] ?? [],
        'block' => $block,
    ])
@endforeach

First use case: a simple marketing page with hero, text, and image blocks. Build those three Blade partials first, add them to the allowed block list, create one page in Filament, and render it through a normal Laravel route. That exercises the full loop—schema, editor, JSON shape, renderer—before you add columns, nesting, or previews.

Where to look first: the README, src/ for the field and renderer classes, any config file for block registration, and the package tests/examples for the exact JSON shape. Confirm the shape with a real saved record before writing renderers.

Implementation Patterns

Keep a single block registry. Define each block type once: its label, icon, allowed fields, validation rules, and Blade partial. Pass that allow-list into the builder field per resource. This prevents editors from inserting a block that has no renderer and gives you one place to add new block types.

Render through partials, not a giant match. A recursive renderer pays off because the package supports columns and nested content (the v0.4.2 release notes mention columns and the form editor deleting or duplicating content inside them). Use a partial that renders one block and recurses into child columns:

{{-- page-builder/block.blade.php --}}
@php($type = $block['type'] ?? null)

@if ($type === 'columns')
    <div class="grid">
        @foreach ($block['data']['columns'] ?? [] as $column)
            @foreach ($column as $child)
                @include('page-builder.block', ['block' => $child])
            @endforeach
        @endforeach
    </div>
@else
    @includeIf("page-builder.blocks.{$type}", ['data' => $block['data'] ?? []])
@endif

Guard unknown types with @includeIf plus a log warning so one bad block does not break an entire page.

Separate page concerns from block content. Keep slug, title, SEO fields, status, and publish dates as normal model columns. Store only ordered block content in the JSON column. That keeps queries, routing, and publishing simple and avoids turning the JSON into an unqueryable mini-database.

Use a draft/published pattern. A common approach is content for the working copy and published_content for the live copy, or a single JSON column plus published_at. Duplicate-page actions are easy because the content is one JSON value—but remember to deep-copy the array, not share references.

Validate block data server-side. Filament will validate field-level input, but add a rule or service that checks each block has a known type and expected keys before saving. If you expose a frontend preview endpoint, validate there too; never render arbitrary type values as a partial path without an allow-list.

Cache rendered pages deliberately. Rendering dozens of blocks on every request is usually fine, but for high-traffic pages cache the final HTML and invalidate on model save. Do not cache the builder editor output itself; editors need fresh state after saves.

Localization: either use one JSON column per locale (content_en, content_es) or store locale-keyed content under a stable shape. Pick one early; retrofitting locale into an already-populated JSON payload is painful.

Media handling: upload images through Filament's FileUpload inside block data and store paths, not full URLs. Add a cleanup job or observer to delete orphaned media when blocks are removed, because JSON payloads do not give you database-level cascade deletion.

Testing pattern: write one Livewire/Filament form test that saves a page with a hero block and one nested columns block, then assert the stored JSON is a list in the expected order and that the frontend route returns the rendered partial output. This is the highest-value test for a package with sparse public documentation.

Gotchas and Tips

  • Version mismatch is the first failure mode. Installing the latest v0.4.x on Filament 3 will not work; use v0.3.1. On Filament 4.12.6+/5.x, require PHP 8.3+.
  • SPA panels and the Form editor link: v0.4.2 specifically fixes Cancel not stopping the Form editor link on SPA panels when a page has columns. The form view can delete or duplicate content inside columns, so that confirmation matters. Upgrade to at least v0.4.2 if you use ->spa() and columns.
  • The JSON shape is the real API. Before writing renderers, save one page with each block type and inspect the raw column with tinker or a DB client. Package docs may not describe nesting, wrapper keys, or block IDs precisely.
  • Treat order as a list, not a map. The package stores ordered blocks; do not re-key the JSON by block ID unless the package explicitly does so. Preserve array order when normalizing or transforming content.
  • Unknown block types should fail soft. A missing partial or renamed block type should log and skip, not throw a 500 on a live page. Add a schema version key to the JSON if you expect block renames later.
  • Rich text is an XSS surface. If a text block uses Filament's RichEditor, sanitize HTML before output. Do not blindly {!! !!} user-authored block content unless your editor roles are fully trusted.
  • Nested columns increase render cost and editor complexity. Limit nesting depth and block count per page. Very large Livewire payloads can make the builder sluggish; test with a realistic page, not three blocks.
  • Orphaned files are easy to create. Removing an image block does not necessarily delete the file. Add a scheduled cleanup or a save observer that diffs old and new media paths.
  • Publishing and caching can hide edits. If you cache rendered HTML, wire invalidation to the model's saved and deleted events. Editors will otherwise report “my change didn't appear” after a successful save.
  • Low adoption means you are partly the maintainer. With 14 stars and 0 dependents, audit the package source before relying on it for client work, pin versions, and keep your block renderers in application code so you can replace the editor later without rewriting the frontend.
  • Extension points to check in source: custom block registration, per-field allowed block configuration, renderer view overrides, and any service-provider bindings. If the package does not expose a clean extension point for a block type you need, prefer adding your own partial and allow-list entry over forking the field class.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle