← All Articles

Dealing with shape shifting data building dynamic tables in Filament

April 03, 2026

3 min read

In a perfect world, every database column is predefined, and every model is predictable. But in the real world, we often deal with EAV (Entity-Attribute-Value) patterns, JSONB metadata, or dynamic form builders where the user defines the fields.

If you try to map these to a standard Filament Table, you'll quickly realize that TextColumn::make('key') doesn't work when "key" changes for every user.

Here is how to build a "Cameleon Table" in Filament v5 that generates its schema on the fly.

The Problem: The Static Schema Trap

Imagine an IoT Monitoring System. Each device sends different telemetry data stored in a jsonb column called payload:

  • Device A (Thermometer): {"temp": 22.5, "humidity": 45}
  • Device B (GPS): {"lat": 44.4, "long": 26.1, "speed": 60}

If you want a single Filament Resource to show a table of all telemetry logs, you can't hardcode the columns. If you do, you'll end up with a mess of empty cells or a single, unreadable TextEntry showing the whole JSON blob.

The Solution: Programmatic Schema Injection

The beauty of Filament is that its table() and form() methods are just PHP. We can use logic to "inject" columns before the table renders.

Step 1: Prepare the Model

Ensure your JSON column is cast properly in your Laravel Model to allow easy collection manipulation.

1// App/Models/TelemetryLog.php
2protected $casts = [
3 'payload' => 'array', // or 'collection'
4];

Step 2: Extract Unique Keys

To build the table headers, we first need to know what keys exist in the data. For performance, you might want to pluck these from a cache or a specific "Definition" model, but for this example, we’ll grab them from the latest records.

1// Inside TelemetryResource.php
2 
3public static function getDynamicColumns(): array
4{
5 // We fetch the keys from the last 50 entries to ensure we cover all types
6 return \App\Models\TelemetryLog::latest()
7 ->limit(50)
8 ->get()
9 ->flatMap(fn ($log) => array_keys($log->payload ?? []))
10 ->unique()
11 ->toArray();
12}

Step 3: Map Keys to Filament Columns

Now, we transform those strings into actual Filament Column objects.

1public static function table(Table $table): Table
2{
3 $dynamicKeys = self::getDynamicColumns();
4 
5 $columns = [
6 // We still keep our static ID/Timestamp columns
7 Tables\Columns\TextColumn::make('created_at')
8 ->dateTime()
9 ->sortable(),
10 Tables\Columns\TextColumn::make('device_id')
11 ->searchable(),
12 ];
13 
14 // Inject the dynamic columns
15 foreach ($dynamicKeys as $key) {
16 $columns[] = Tables\Columns\TextColumn::make("payload.{$key}")
17 ->label(ucfirst($key))
18 ->sortable()
19 ->toggleable() // Highly recommended for dynamic tables!
20 ->placeholder('N/A');
21 }
22 
23 return $table
24 ->columns($columns)
25 ->filters([ /* ... */ ])
26 ->actions([ /* ... */ ]);
27}

The "Extreme" Hint: Type-Sensitive Columns

What if one JSON key is a boolean (status) and another is a number (price)? You can take this further by inspecting the value type of the first occurrence and returning a specific Filament Column type.

1foreach ($dynamicKeys as $key) {
2 $firstValue = $sampleData->firstWhere($key)['value'];
3 
4 $columns[] = match(gettype($firstValue)) {
5 'boolean' => Tables\Columns\IconColumn::make("payload.{$key}")->boolean(),
6 'double', 'integer' => Tables\Columns\TextColumn::make("payload.{$key}")->numeric(),
7 default => Tables\Columns\TextColumn::make("payload.{$key}")->limit(30),
8 };
9}

Performance Warning

Fetching 50 records every time the table loads just to get keys is expensive.

Pro Tip: Cache the dynamicKeys list and clear it only when a new type of data is ingested.

Database Tip: If using PostgreSQL, use a jsonb_object_keys query for much faster key extraction at scale.

Summary

By treating the columns() array as a dynamic object rather than a static list, you can build interfaces that adapt to your data in real-time. This turns Filament from a simple CRUD tool into a powerful, data-agnostic dashboard engine.

Newsletter

Get notified when new components and articles are published. No spam, just Filament goodness.

We will send you a confirmation link before you are subscribed.