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, State, and Performance at Scale

 Filament v3 Custom Table Columns: Rendering, State, and Performance at Scale
=============================================================================

 Go beyond built-in columns in Filament v3. Learn how to build custom table columns with precise state resolution, efficient eager loading, and clean render logic that holds up under real production load.

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

ShareCopy linkCopied

 ![Filament v3 Custom Table Columns: Rendering, State, and Performance at Scale](https://cdn.msaied.com/406/145404572d600dbdab49a13226b0537d.png) 

  On this page +1. [Why Custom Columns Deserve More Attention](#why-custom-columns-deserve-more-attention)
2. [Anatomy of a Custom Column](#anatomy-of-a-custom-column)
3. [Declaring Eager Load Relationships](#declaring-eager-load-relationships)
4. [Fluent Configuration Methods](#fluent-configuration-methods)
5. [Keeping Views Fast](#keeping-views-fast)
6. [Registering the Column for Auto-Discovery](#registering-the-column-for-auto-discovery)
7. [Takeaways](#takeaways)

 Why Custom Columns Deserve More Attention
-----------------------------------------

Filament's built-in columns cover 80% of cases, but the remaining 20% — composite data, computed badges, relationship-derived icons — quickly becomes a mess of `->html()` hacks and anonymous closures scattered across your resource. A proper custom column class gives you a reusable, testable, IDE-friendly primitive.

This article focuses on the mechanics that matter in production: state resolution, eager loading declarations, and keeping Blade views lean.

---

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

Every custom column extends `Filament\Tables\Columns\Column`. The minimum surface you need to understand:

- `getState()` — resolves the column's value from the record
- `getExtraAttributes()` — merges HTML attributes onto the cell
- The Blade view referenced by `$view`

```php
namespace App\Filament\Tables\Columns;

use Filament\Tables\Columns\Column;

class SubscriptionStatusColumn extends Column
{
    protected string $view = 'filament.tables.columns.subscription-status';

    public function getState(): mixed
    {
        $record = $this->getRecord();

        return [
            'label' => $record->subscription?->plan->name ?? 'Free',
            'active' => $record->subscription?->isActive() ?? false,
            'trial' => $record->subscription?->onTrial() ?? false,
        ];
    }
}

```

The Blade view receives `$getState` as a closure:

```blade
@php
    $state = $getState();
@endphp

 $state['active'] && !$state['trial'],
    'bg-yellow-100 text-yellow-800' => $state['trial'],
    'bg-gray-100 text-gray-500' => !$state['active'],
])>
    {{ $state['label'] }}

```

---

Declaring Eager Load Relationships
----------------------------------

This is where most custom column implementations fall apart. If your `getState()` touches a relationship, every row triggers a lazy load. Filament provides `->relationship()` on built-in columns, but for custom columns you must override `getRelationships()`:

```php
public function getRelationships(): array
{
    return ['subscription', 'subscription.plan'];
}

```

Filament's table builder calls `getRelationships()` on every column and merges the results into a single `with()` call before executing the query. Declare nested dot-notation paths exactly as you would in Eloquent.

If your column conditionally touches different relationships based on a configuration closure, resolve the closure inside `getRelationships()` before returning:

```php
public function getRelationships(): array
{
    $extra = value($this->extraRelationship);

    return array_filter([
        'subscription',
        'subscription.plan',
        $extra,
    ]);
}

```

---

Fluent Configuration Methods
----------------------------

Custom columns should feel native. Add fluent setters using the `Macroable`-style pattern Filament itself uses — store values in `$this->evaluate()`-compatible closures so they support both static values and record-aware closures:

```php
protected bool | Closure $showTrialBadge = true;

public function showTrialBadge(bool | Closure $show = true): static
{
    $this->showTrialBadge = $show;

    return $this;
}

public function isShowingTrialBadge(): bool
{
    return $this->evaluate($this->showTrialBadge);
}

```

Passing `$this->evaluate()` a closure automatically injects the current record, so callers can write:

```php
SubscriptionStatusColumn::make('subscription_status')
    ->showTrialBadge(fn ($record) => $record->created_at->isAfter(now()->subDays(30)))

```

---

Keeping Views Fast
------------------

Blame slow tables on views that call PHP methods per cell. Rules:

1. **Resolve once** — call `$getState()` once at the top of the view and destructure.
2. **No Eloquent in views** — all relationship data must come through `getState()`.
3. **Avoid `@livewire` inside column views** — each cell is already inside a Livewire component; nesting adds wire overhead.
4. **Cache computed values in `getState()`** if the column is used in sortable or searchable contexts where it may be called multiple times per request.

---

Registering the Column for Auto-Discovery
-----------------------------------------

If you ship this inside a package or a shared module, register it in a service provider so teams can use it without imports:

```php
use Filament\Support\Facades\FilamentAsset;

public function boot(): void
{
    FilamentAsset::register([
        // register any JS/CSS assets here if your column needs them
    ]);
}

```

For in-app columns, a simple `use` statement is sufficient — no registration needed.

---

Takeaways
---------

- Override `getRelationships()` to declare eager loads; skipping this causes N+1 at the column level.
- Store configurable options as `bool | Closure` and resolve via `$this->evaluate()` for record-aware flexibility.
- Keep Blade views dumb: resolve all state in `getState()`, destructure once at the top of the view.
- Fluent setters make custom columns feel native and keep resource files readable.
- Test `getState()` directly by instantiating the column, setting a mock record, and asserting the returned array.

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

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

  Why does my custom column still trigger N+1 queries even after overriding getRelationships()?Check that the relationship names returned exactly match the Eloquent relation method names on your model, including nested dot-notation paths. A typo silently skips the eager load. Also verify you are not calling additional relationships inside the Blade view itself.

   Can I make a custom column sortable or searchable?Yes. Call -&gt;sortable() or -&gt;searchable() as usual, but provide a custom sort or search query callback when the column state is computed rather than a direct database column: -&gt;sortable(query: fn ($query, $direction) =&gt; $query-&gt;orderBy('subscriptions.status', $direction)).

   How do I write a Pest test for a custom column's getState() output?Instantiate the column with ::make('name'), call -&gt;record($model) to inject a model, then assert the return value of getState(). No Livewire test harness is needed for pure state logic — only bring in livewire() helpers when testing the rendered table interaction.

   ![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 articlePasswordless Sign-In with Fortify Two-Factor Support in Laravel](https://www.msaied.com/public/articles/passwordless-sign-in-with-fortify-two-factor-support-in-laravel) [Next articleLaravel Queues: Reliable Job Retry Strategies with Exponential Backoff and Dead-Letter Handling](https://www.msaied.com/public/articles/laravel-queues-reliable-job-retry-strategies-with-exponential-backoff-and-dead-letter-handling)  

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

1. [Why Custom Columns Deserve More Attention](#why-custom-columns-deserve-more-attention)
2. [Anatomy of a Custom Column](#anatomy-of-a-custom-column)
3. [Declaring Eager Load Relationships](#declaring-eager-load-relationships)
4. [Fluent Configuration Methods](#fluent-configuration-methods)
5. [Keeping Views Fast](#keeping-views-fast)
6. [Registering the Column for Auto-Discovery](#registering-the-column-for-auto-discovery)
7. [Takeaways](#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)
