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 Content Builder Laravel Package

van-ons/filament-content-builder

A Filament plugin for building flexible, block-based content in Laravel. Compose pages and rich content from reusable components, manage layouts directly in the admin panel, and render structured content without custom CMS boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Filament block/content package: you define blocks as PHP classes, attach them to a model or page, and render them in Blade. v2.7.0 is additive — visible() and hidden() on blocks — so anything you know from earlier 2.x applies.

  1. Install and register

    composer require <vendor>/filament-blocks   # confirm exact name from the README badge
    php artisan vendor:publish --tag="filament-blocks-config"   # if the package ships config/views
    

    Register the plugin on the panel(s) that should expose block editing (usually AdminPanelProvider, sometimes also a front-end panel):

    ->plugins([
        \Vendor\FilamentBlocks\FilamentBlocksPlugin::make(),
    ])
    
  2. Look here first, in this order

    • README.md — block registration and the HasBlocks-style trait/relationship are always documented there.
    • config/filament-blocks.php — view namespace, default block model, render wrapper, caching flags.
    • src/Blocks/ — the base Block class. Read it before writing your first block; every fluent method you'll chain (visible(), hidden(), layout/schema helpers) is defined there, and it's the fastest way to see what's actually supported rather than guessing from docs.
  3. First use case: one block, one page

    class HeroBlock extends Block
    {
        protected static ?string $name = 'Hero';
    
        public function schema(): array
        {
            return [
                TextInput::make('heading')->required(),
                RichEditor::make('body'),
            ];
        }
    }
    

    Attach it to the page/model via the package's relation, then render in the Blade view:

    @foreach ($page->blocks as $block)
        {!! $block->render() !!}
    @endforeach
    

    Ship that to a staging environment before doing anything clever. The block-editing UI, ordering, and draft/publish behaviour are where most of the day-to-day value (and most of the surprises) live.

Implementation Patterns

Blocks as content types, not page sections. The pattern that scales is one block class per reusable content unit (HeroBlock, RichTextBlock, CtaBlock, FaqBlock), with the page deciding composition. Resist the urge to build a "PageBlock" with 30 optional fields — the block schema becomes untestable and content editors get lost.

Fields vs. presentation. Keep schema() limited to what an editor should change. Everything else (wrapper classes, grid spans, view selection) belongs in the block class or its Blade view, not in the form. If you need variants, add a Select::make('variant') and switch views in the render method rather than exposing layout controls.

Conditional rendering — the new 2.7 feature. This is the release's real win: it removes the @if clutter from your Blade templates by moving visibility into the block definition.

public function visible(): bool
{
    return auth()->check();
}

Use it for the obvious cases — members-only blocks, locale-specific blocks, campaign blocks with a date window, A/B variants. The value is that visibility becomes content-time configuration rather than template-time logic, so a block can be authored now and appear later without a deploy.

Practical pairings:

  • Scheduled content: visible() returning now()->between($this->starts_at, $this->ends_at) for announcement banners. Simpler than a scheduler and it self-heals.
  • Environment/staging guards: hide experimental blocks in production without deleting them.
  • Feature flags: visible() reading a Pennant/Feature::active() flag — instant rollback of a block without touching the database.
  • Per-locale: hide blocks that don't have a translation yet.

Keep visible() cheap and side-effect free. It runs during render, so it may run once per block per request (and again per cached fragment). No queries inside it that you can avoid, no writes, no dump().

Composition and ordering. Blocks carry a sort column; use the package's reorder UI rather than re-saving records manually. If you need deterministic ordering in tests, set the sort explicitly in your factory.

Render caching. Wrap {!! $block->render() !!} in Cache::remember() keyed on block ID + updated_at when blocks get heavy (image processing, nested queries). If you do, remember that visible() result must be part of the cache key or computed outside the cached fragment — otherwise a block hidden by a date window can be served from cache after it should have disappeared.

Integration with policies. For member-only content, don't rely solely on visible(); it's a rendering concern. Gate the underlying data too, so a leaked block ID can't be fetched via a Livewire component.

Testing. Assert on block classes rather than rendered HTML where you can:

expect((new MembersOnlyBlock)->visible())->toBeFalse();
$this->actingAs($user);
expect((new MembersOnlyBlock)->visible())->toBeTrue();

And a handful of feature tests hitting the page to confirm hidden blocks produce no markup at all (not just display:none).

Gotchas and Tips

Hidden vs. unpublished are different things. visible()/hidden() is a rendering decision. It does not change the block's stored state, and if you're iterating $page->blocks in a custom template you may bypass the visibility check entirely — verify that the package's own render path is what applies it, and if you loop manually, filter with the same predicate. This is the single most likely source of "the hidden block is still showing" bugs.

hidden() does not scrub the DOM reliably. If the implementation renders then hides via CSS rather than skipping the block, sensitive content can still be in the page source. Check the rendered output of a hidden block in 2.7 before using it for anything access-control-adjacent.

Precedence between visible() and hidden(). When both are set (say through a page-level default and a block-level override), don't assume which wins. Read the base class once and write the rule down in your own README; future-you will otherwise flip it accidentally.

Context is not always available. visible() may be evaluated in contexts without an authenticated user or request — console commands, queued jobs, static site generation, sitemap builders. Anything calling auth()->user()->... unguarded will throw there. Use auth()->check() first, or auth()->user()?->.

N+1 on block relations. Rendering a list of pages with blocks is a classic N+1. Eager load (->with('blocks')) and, if blocks reference media or related models, load those too. The package won't do it for you.

View namespace resolution. Custom block views resolve through a registered namespace. If your override silently doesn't apply, you've almost certainly got the wrong namespace prefix, or a published view in resources/views/vendor/... is shadowing it. php artisan view:clear after publishing saves a confusing ten minutes.

Config caching. Any config change needs php artisan config:clear in local dev. A stale cached config pointing at the wrong block model produces confusing "class not found" errors that look like a broken install.

Extension points worth knowing. The base Block class, the plugin's ->blocks([...]) registration, and the published views are the three levers. Prefer subclassing a block and overriding its render/view over patching vendor files. If you need cross-cutting behaviour (analytics, wrapper markup, lazy loading), a small base class in your app that all your blocks extend is cleaner than repeating logic in visible() on every class.

Version 2.7 is low-risk to adopt. The release is one feature plus dependency and CI housekeeping — notably the removal of a third-party Filament field library from the dev/dependency set (van-ons/filament-field-library). If your blocks' schemas used fields that only existed via that transitive dependency, they'll break on upgrade: run your block form tests and load the editor for each block type on staging before deploying. Otherwise this is a safe patch to pick up, and the visibility methods are worth adopting incrementally rather than retrofitting every block at once.

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.
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
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle