Skip to content

Advanced Filament: Custom Field Plugins, Custom Columns, and Render Hooks

Go beyond built-in components: build a reusable Filament custom field plugin, register a custom table column with live state, and wire render hooks to inject UI without forking core.

Published · Updated · 3 min read

Advanced Filament: Custom Field Plugins, Custom Columns, and Render Hooks

Why Extend Filament at the Component Level

Filament ships with a rich set of fields and columns, but production panels inevitably need components that don't exist yet — a colour-swatch picker, a rich diff viewer, a sparkline column. The wrong move is to fork core or paste raw Blade into a resource. The right move is to build a proper plugin that can be versioned, tested, and shared.

This article covers three extension points: a custom Form field plugin, a custom Table column, and render hooks for surgical UI injection.


1. Custom Form Field Plugin

A Filament field is a class that extends Filament\Forms\Components\Field and pairs with a Blade view.

// src/Forms/Components/ColourSwatchPicker.php
namespace Acme\FilamentColour\Forms\Components;

use Filament\Forms\Components\Field;

class ColourSwatchPicker extends Field
{
    protected string $view = 'filament-colour::forms.components.colour-swatch-picker';

    /** @var array<string> */
    protected array $swatches = [];

    public function swatches(array $colours): static
    {
        $this->swatches = $colours;
        return $this;
    }

    public function getSwatches(): array
    {
        return $this->swatches;
    }
}

The Blade view receives $field automatically:

{{-- resources/views/forms/components/colour-swatch-picker.blade.php --}}
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
    <div x-data="{ selected: @entangle($getStatePath()) }" class="flex gap-2">
        @foreach ($field->getSwatches() as $colour)
            <button
                type="button"
                :class="selected === '{{ $colour }}' ? 'ring-2 ring-offset-1' : ''"
                style="background:{{ $colour }}"
                class="w-8 h-8 rounded-full"
                @click="selected = '{{ $colour }}'"
            ></button>
        @endforeach
    </div>
</x-dynamic-component>

Register it in your plugin's service provider so auto-discovery works:

public function packageBooted(): void
{
    Filament::registerRenderHook('panels::body.end', fn () => '');
    // Blade component registration happens via package view namespace
}

Usage in a resource:

ColourSwatchPicker::make('brand_colour')
    ->swatches(['#ef4444', '#3b82f6', '#22c55e'])
    ->required(),

2. Custom Table Column

Custom columns extend Filament\Tables\Columns\Column and follow the same view convention.

namespace Acme\FilamentColour\Tables\Columns;

use Filament\Tables\Columns\Column;

class ColourSwatchColumn extends Column
{
    protected string $view = 'filament-colour::tables.columns.colour-swatch-column';
}
{{-- tables/columns/colour-swatch-column.blade.php --}}
<div
    class="w-6 h-6 rounded-full border border-gray-200"
    style="background: {{ $getState() }}"
    title="{{ $getState() }}"
></div>

Because $getState() resolves through Filament's normal attribute pipeline, sorting, searching, and formatStateUsing() all work without extra effort.

ColourSwatchColumn::make('brand_colour')
    ->label('Brand')
    ->sortable(),

3. Render Hooks for Surgical UI Injection

Render hooks let you inject Blade output at named slots across every Filament panel without touching a single core file. They are registered in a service provider or panel provider.

use Filament\Support\Facades\FilamentView;
use Illuminate\Support\Facades\Blade;

FilamentView::registerRenderHook(
    'panels::topbar.end',
    fn (): string => Blade::render('<livewire:environment-badge />'),
);

Available hooks include panels::body.start, panels::sidebar.nav.start, panels::page.start, and many more — check the Filament docs for the full list per version.

For hooks that should only fire on specific pages, gate them:

use Filament\Pages\Page;

FilamentView::registerRenderHook(
    'panels::page.end',
    fn (): string => Blade::render('<livewire:audit-trail-drawer />'),
    scopes: App\Filament\Resources\OrderResource\Pages\EditOrder::class,
);

Packaging It All Together

Wrap everything in a Spatie Laravel Package Tools plugin:

public function configurePackage(Package $package): void
{
    $package
        ->name('filament-colour')
        ->hasViews()
        ->hasConfigFile();
}

Filament's own plugin interface (FilamentPlugin) lets you hook into panel registration cleanly, giving you access to the panel instance for conditional logic.


Key Takeaways

  • Extend Field or Column and pair with a namespaced Blade view — no hacks needed.
  • @entangle($getStatePath()) is the correct Alpine bridge for two-way field state.
  • Render hooks are scoped to panels and pages, keeping injection surgical and reversible.
  • Package everything with Spatie Package Tools; Filament's auto-discovery handles the rest.
  • Custom columns inherit sorting, searching, and formatStateUsing() for free.

Frequently asked questions

Can a custom Filament field support validation rules like built-in fields?

Yes. Because your field extends `Filament\Forms\Components\Field`, you can chain any built-in rule method (`->required()`, `->rules([])`, `->minLength()`) and Filament's form validation pipeline treats it identically to a native field.

How do render hooks differ between Filament v3 and v4?

The hook names and the registration API are largely the same, but v4 introduced additional schema-level hooks tied to the unified Schema API. Always check the version-specific hook reference; hooks added in v4 will silently do nothing in a v3 panel.

Is it safe to use Livewire components inside render hooks?

Yes, using `Blade::render('<livewire:my-component />')` inside a render hook works, but each call mounts a full Livewire component. Keep them lightweight and avoid mounting the same component on every page load if it carries heavy state.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project