Skip to content

Custom Sections

Custom sections are first-class citizens. Drop a class anywhere on the autoloader, extend BladeSection, and FilamentCraft renders it in the editor and on published pages — the same pipeline the built-in sections use.

A minimal section

php
namespace App\Sections;

use FilamentCraft\Sections\BladeSection;
use FilamentCraft\Settings\Types\Image;
use FilamentCraft\Settings\Types\Text;
use FilamentCraft\Settings\Types\Textarea;

final class TestimonialSection extends BladeSection
{
    protected static string $view = 'sections.testimonial';

    public static function slug(): string
    {
        return 'testimonial';
    }

    public static function name(): string
    {
        return 'Testimonial';
    }

    public static function settings(): array
    {
        return [
            Textarea::make('quote')->label('Quote')->required(),
            Text::make('author')->label('Author')->required(),
            Image::make('avatar')->label('Avatar'),
        ];
    }

    public static function defaults(): array
    {
        return [
            'settings' => [
                'quote' => 'A useful quote about the product.',
                'author' => 'Customer Name',
            ],
            'blocks' => [],
        ];
    }
}

The matching Blade view (resources/views/sections/testimonial.blade.php) receives a $section data object:

blade
<figure class="px-6 py-12 text-center">
    <blockquote class="text-2xl">{{ $section->settings->get('quote') }}</blockquote>
    <figcaption class="mt-4 text-sm text-gray-500">{{ $section->settings->get('author') }}</figcaption>
</figure>
A custom section's settings panel
A custom section's settings panel, generated from its static schema.

Scaffold it

php artisan make:filamentcraft-section Testimonial generates the class and Blade view (or a Livewire class with --livewire). See Make Commands.

The Section contract

BladeSection implements FilamentCraft\Sections\Contracts\Section via the SectionSchema trait, which supplies sensible defaults for everything except the view. Override only what you need:

MethodReturnsDefaultPurpose
slug()stringkebab of class name minus -sectionStable id stored in revisions.
name()stringheadline of the slugDisplay label in the catalog.
category()string'general'Catalog grouping.
description()?stringnullSub-label in the catalog.
icon()stringheroicon-o-puzzle-pieceCatalog icon.
maxBlocks()int16Cap on total blocks across all block types.
wrapper()string'section'The HTML tag/selector wrapping the rendered output.
settings()array[]The settings schema (see below).
blocks()array[]Repeatable child blocks.
presets()array[]Named starting points.
enabledOn()array['*']Template types the section is offered on.
disabledOn()array[]Template types to exclude.
previewImageUrl()?stringnullCatalog thumbnail.
defaults()array['settings' => [], 'blocks' => []]Initial content when added.

slug(), name(), settings(), and defaults() are the ones you'll usually define.

Settings

settings() returns an array of setting types. It can mix the FilamentCraft DSL with raw Filament v4 components — the compiler wires live() on every leaf field so the preview keeps updating, and resolves visibility from either the DSL's visibleIf([...]) shortcut or Filament's native visible(Closure) callback.

php
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Utilities\Get;
use FilamentCraft\Settings\Types\Group;
use FilamentCraft\Settings\Types\Text;

public static function settings(): array
{
    return [
        Group::make('content')->label('Content'),
        Text::make('heading')->label('Heading')->default('Welcome'),

        Group::make('cta')->label('Call to action')->collapsed(),
        Toggle::make('cta_enabled')->label('Show button')->default(true),
        Text::make('cta_label')->label('Button label')
            ->visibleIf(['cta_enabled' => true]),                       // DSL shortcut
        Text::make('cta_note')->label('Note')
            ->visible(fn (Get $get): bool => (bool) $get('cta_enabled')), // native closure
    ];
}

Use Group to break a long form into labelled clusters — see Layout & Structure.

Blocks

A block is a repeatable child item (a nav link, a pricing plan, a quote). Declare them with Block::make('slug'), each with its own settings schema and an optional limit:

php
use FilamentCraft\Sections\Block;
use FilamentCraft\Settings\Types\Text;
use FilamentCraft\Settings\Types\Link;

public static function blocks(): array
{
    return [
        Block::make('nav-link')
            ->name('Nav link')
            ->limit(6)
            ->settings([
                Text::make('label')->label('Label')->default('Page'),
                Link::make('url')->label('URL')->default('/'),
            ]),
    ];
}

Read blocks in the Blade view from $section->blocks. The maxBlocks() cap (default 16) limits the total number of blocks added to one section instance, across every block type.

Presets

The Browse presets modal
The Hero section's two presets — SaaS Spotlight and Editorial Split — in the Browse presets modal.

A preset is a named bundle of settings and blocks the editor can apply in one click. Return Preset objects from presets():

php
use FilamentCraft\Sections\Preset;

public static function presets(): array
{
    return [
        Preset::make('split-header', 'Split Header')
            ->settings([
                'brand_text' => 'Acme',
                'cta_enabled' => true,
            ])
            ->blocks([
                ['id' => 'nav-1', 'type' => 'nav-link', 'settings' => ['label' => 'Product', 'url' => '/product']],
                ['id' => 'nav-2', 'type' => 'nav-link', 'settings' => ['label' => 'Pricing', 'url' => '/pricing']],
            ]),
    ];
}

In the editor, presets surface behind the Browse presets button on the section's header — each one is a one-click starting point.

Registering a section

Two ways — both are calls on the plugin instance in your panel provider:

php
use FilamentCraft\FilamentCraftPlugin;

public function panel(Panel $panel): Panel
{
    return $panel->plugin(
        FilamentCraftPlugin::make()
            ->registerSection(\App\Sections\TestimonialSection::class)
            // …or auto-discover a whole directory:
            ->discoverSectionsIn(app_path('Sections'))
    );
}

The conventional path app/Sections/ is auto-scanned with no extra config.

Extending a built-in section

The bundled sections are intentionally subclassable. Give the subclass its own slug, optionally point it at a custom view, and override only what you want to change:

php
namespace App\Sections;

use FilamentCraft\Sections\Builtin\HeroSection;

class ProductHeroSection extends HeroSection
{
    protected static string $view = 'sections.product-hero';

    public static function slug(): string
    {
        return 'product-hero';
    }

    public static function name(): string
    {
        return 'Product hero';
    }
}

Register the subclass with ->registerSection(ProductHeroSection::class). It inherits the parent's settings, blocks, and presets unless you override those methods too.

Proprietary — distributed via Anystack.