Filament v3 Custom Table Columns Deep Dive | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Filament v3 Custom Table Columns: Rendering Complex UI Without Hacks

 Filament v3 Custom Table Columns: Rendering Complex UI Without Hacks
=====================================================================

 Learn how to build fully custom Filament v3 table columns with Blade views, state callbacks, and Alpine.js — without reaching for render hooks or abusing existing column types.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 30 Aug 2026 · Updated 30 Aug 2026 · 3 min read

ShareCopy linkCopied

 ![Filament v3 Custom Table Columns: Rendering Complex UI Without Hacks](https://cdn.msaied.com/611/05db05ad084cfdd9a407ff7707dcfaf7.png) 

  On this page +1. [Why Custom Columns Exist](#why-custom-columns-exist)
2. [Anatomy of a Custom Column](#anatomy-of-a-custom-column)
3. [The Blade View](#the-blade-view)
4. [Registering and Using the Column](#registering-and-using-the-column)
5. [Sortable and Searchable Support](#sortable-and-searchable-support)
6. [Testing the Column](#testing-the-column)
7. [Key Takeaways](#key-takeaways)

 Why Custom Columns Exist
------------------------

Filament ships with `TextColumn`, `BadgeColumn`, `ImageColumn`, and a handful of others. They cover 80% of cases. The remaining 20% — sparklines, inline progress bars, multi-line compound cells, real-time status indicators — require you to either abuse `TextColumn::make()->html()` or build a proper custom column.

Abusing `->html()` works until it doesn't: no Alpine state, no scoped CSS, no reusability, and a security surface you have to sanitise manually. Building a real column takes about 30 minutes and pays dividends across every resource that needs it.

Anatomy of a Custom Column
--------------------------

A Filament table column is a PHP class that extends `Filament\Tables\Columns\Column` and pairs with a Blade view. The framework calls `->render()` on each column per row, passing a `$state` variable derived from the record.

```php
// app/Tables/Columns/StatusBadgeColumn.php
namespace App\Tables\Columns;

use Filament\Tables\Columns\Column;

class StatusBadgeColumn extends Column
{
    protected string $view = 'tables.columns.status-badge';

    protected \Closure|string|null $colorCallback = null;

    public function color(\Closure|string $color): static
    {
        $this->colorCallback = $color;
        return $this;
    }

    public function getColor(): string
    {
        $state = $this->getState();

        return $this->evaluate($this->colorCallback, [
            'state' => $state,
            'record' => $this->getRecord(),
        ]) ?? 'gray';
    }
}

```

The `evaluate()` helper is inherited from `Filament\Support\Concerns\EvaluatesClosures`. It resolves both plain values and closures, injecting named parameters from the array you pass — exactly how core columns work internally.

The Blade View
--------------

```blade
{{-- resources/views/tables/columns/status-badge.blade.php --}}
@php
    $color = $getColumn()->getColor();
    $state = $getState();
@endphp

        {{ $state }}

        Status: {{ $state }}

```

Filament injects `$getColumn()`, `$getState()`, `$getRecord()`, and `$livewire` into every column view automatically. You never need to pass them manually.

Registering and Using the Column
--------------------------------

No service provider registration is needed. Import and use directly:

```php
use App\Tables\Columns\StatusBadgeColumn;

public static function table(Table $table): Table
{
    return $table->columns([
        TextColumn::make('name'),
        StatusBadgeColumn::make('status')
            ->color(fn (string $state): string => match ($state) {
                'active'   => 'green',
                'banned'   => 'red',
                'pending'  => 'yellow',
                default    => 'gray',
            }),
    ]);
}

```

Sortable and Searchable Support
-------------------------------

Custom columns inherit `->sortable()` and `->searchable()` for free because those traits operate on the underlying database column name, not the view. If your column name maps 1:1 to a database column, they just work.

For computed or joined columns, pass an explicit sort callback:

```php
StatusBadgeColumn::make('status')
    ->sortable(query: fn ($query, $direction) =>
        $query->orderBy('status', $direction)
    )

```

Testing the Column
------------------

```php
it('renders the correct badge color for banned users', function () {
    $user = User::factory()->create(['status' => 'banned']);

    livewire(UserResource\Pages\ListUsers::class)
        ->assertCanSeeTableRecords([$user])
        ->assertTableColumnStateSet('status', 'banned', record: $user);
});

```

Filament's Pest helpers assert on state, not rendered HTML, which keeps tests resilient to styling changes.

Key Takeaways
-------------

- Extend `Column`, declare `$view`, and use `evaluate()` for closure-or-value properties.
- Blade views receive `$getColumn()`, `$getState()`, and `$getRecord()` automatically.
- Alpine.js works inside column views without any extra wiring.
- `->sortable()` and `->searchable()` are inherited; override with callbacks for computed columns.
- Test column state, not rendered markup, for durable assertions.

- [filament](https://www.msaied.com/public/articles?search=filament)
- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [livewire](https://www.msaied.com/public/articles?search=livewire)
- [alpine](https://www.msaied.com/public/articles?search=alpine)

 Frequently asked questions 
---------------------------

  Can I use Livewire actions inside a custom column view?Not directly — column views are rendered per-row inside a Livewire component but are not themselves Livewire components. Use Alpine.js for client-side interactivity, or wire up a Filament table action triggered from a button inside the column view using `wire:click` pointing to the parent Livewire component's action.

   How do I pass additional data from the record to the column view?Add a method to your column class that calls `$this-&gt;getRecord()` or accepts a closure via `evaluate()`. Expose it as a public method and call `$getColumn()-&gt;yourMethod()` inside the Blade view. This keeps logic in PHP and the view purely presentational.

   Does this approach work in Filament v4?The class-based extension model is similar in v4, but v4 introduces the unified Schema API and some view variable names have changed. The patterns here are specific to v3. For v4, check the official upgrade guide before migrating custom columns.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)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](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleCQRS Without Event Sourcing: Practical Read/Write Model Separation in Laravel](https://www.msaied.com/public/articles/cqrs-without-event-sourcing-practical-readwrite-model-separation-in-laravel) [Next articleLaravel Octane + FrankenPHP: Persistent Services, Shared State, and Safe Singletons](https://www.msaied.com/public/articles/laravel-octane-frankenphp-persistent-services-shared-state-and-safe-singletons)  

   On this page
-------------

1. [Why Custom Columns Exist](#why-custom-columns-exist)
2. [Anatomy of a Custom Column](#anatomy-of-a-custom-column)
3. [The Blade View](#the-blade-view)
4. [Registering and Using the Column](#registering-and-using-the-column)
5. [Sortable and Searchable Support](#sortable-and-searchable-support)
6. [Testing the Column](#testing-the-column)
7. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

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

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

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

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
