Stop relying on Eloquent models: build Filament v4 tables directly from APIs, arrays, or raw queries (and why you should)

Filament v4 has quietly removed one of its biggest constraints: you no longer need an Eloquent model to power a table. Feed your tables directly from external APIs, raw database queries, or simple PHP arrays — while keeping full search, sorting, filters, bulk actions, and pagination.

Stop relying on Eloquent models: build Filament v4 tables directly from APIs, arrays, or raw queries (and why you should)

For years, the standard way to create a table in Filament was simple: create a model, create a Resource, and let Filament do the rest.
That worked great… until it didn’t.

Large datasets, third-party APIs, read-only views, or complex reporting queries made the traditional Eloquent-only approach feel restrictive and slow.
Filament v4 finally breaks that limitation: you no longer need an Eloquent model (or even a Resource tied to one) to power a table.

This feature – officially called custom data sources – landed in v4.0 and is one of the most underrated changes in the entire release.

Why this matters in 2025

  • You can pull data from external APIs (Stripe, Shopify, OpenAI usage, etc.)
  • You can use raw DB::table() or Query Builder for complex reporting without creating dummy models
  • You can display simple PHP arrays (perfect for dashboards or static data)
  • You get full Filament table power: searching, sorting, filters, bulk actions, columns, actions – all of it
  • Performance is often better because you skip Eloquent hydration entirely

Let’s build three real-world examples.

Example 1: Table from a public JSON API (no model, no database)

// app/Filament/Resources/ExchangeRateResource.php
use Filament\Resources\Resource;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Support\Facades\Http;

class ExchangeRateResource extends Resource
{
    public static function table(Table $table): Table
    {
        return $table
            ->query(fn () => new \Illuminate database\Query\Builder) // dummy, will be ignored
            ->headerActions([
                \Filament\Tables\Actions\Action::make('refresh')
                    ->action(fn () => cache()->forget('exchange-rates'))
            ])
            ->poll('30s')
            ->modifyQueryUsing(fn () => null) // we replace everything
            ->contentGrid(['md' => 2])
            ->recordUrl(null)
            ->columns([
                TextColumn::make('currency')
                    ->label('Currency')
                    ->sortable(),
                TextColumn::make('rate')
                    ->label('Rate (EUR)')
                    ->money('eur')
                    ->sortable(),
            ]);
    }

    public static function getTableRecords(): \Illuminate\Support\Collection
    {
        return cache()->remember('exchange-rates', now()->addMinutes(15), function () {
            $response = Http::get('https://api.exchangeratesapi.io/v1/latest', [
                'access_key' => config('services.exchangerates.key'),
                'base' => 'EUR',
            ])->json();

            return collect($response['rates'] ?? [])->map(fn ($rate, $currency) => [
                'currency' => $currency,
                'rate' => $rate,
            ]);
        });
    }
}

That’s it. A fully functional, searchable, sortable table powered by a third-party API. No model, no migration, no Eloquent overhead.

Example 2: Raw database query (reporting dashboard)

use Illuminate\Support\Facades\DB;

public static function getTableRecords(): \Illuminate\Database\Eloquent\Collection
{
    return collect(DB::select("
        SELECT 
            DATE(created_at) as date,
            COUNT(*) as total_orders,
            SUM(total) as revenue,
            AVG(total) as avg_order_value
        FROM orders 
        WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
        GROUP BY DATE(created_at)
        ORDER BY date DESC
    "))->map(fn ($row) => (array) $row);
}

You get charts-ready data with zero Eloquent hydration cost.

Example 3: Simple PHP array (internal tools & prototypes)

public static function getTableRecords(): \Illuminate\Support\Collection
{
    return collect([
        ['server' => 'web-01', 'status' => 'healthy', 'load' => 1.2, 'uptime' => '99.98%'],
        ['server' => 'web-02', 'status' => 'warning', 'load' => 6.8, 'uptime' => '98.12%'],
        ['server' => 'db-01',  'status' => 'healthy', 'load' => 0.9, 'uptime' => '100%'],
    ]);
}

Perfect for internal status dashboards or when you’re prototyping.

How it actually works under the hood

Filament v4 introduced two new methods on any Table:

->modifyQueryUsing(fn ($query) => $query->where('fake', 'to-prevent-eloquent')) // old way

is now replaced by simply overriding:

public static function getTableRecords(): Collection|LengthAwarePaginator
{
    // return whatever you want
}

Filament detects this method and completely bypasses the normal query builder.
You still get full access to:

  • Searching (->searchable() works on array keys)
  • Sorting (->sortable() works out of the box)
  • Filters, bulk actions, row actions
  • Pagination (just return a LengthAwarePaginator if needed)

When you should (and shouldn’t) use this

Do use it when:

  • Data comes from external APIs
  • You’re building reporting dashboards with complex SQL
  • You need read-only tables
  • Performance matters and you want to skip Eloquent entirely

Don’t use it when:

  • You need full CRUD (create/edit/delete) – those still require a proper Resource + Model
  • You want relation managers (they still need Eloquent)
  • You rely heavily on policies/scopes that are model-based

Final thoughts

Filament v4’s custom data sources quietly remove one of the biggest constraints the framework had in v3.
It turns Filament from “just another admin panel” into a legitimate full-stack TALL dashboard framework that can compete with tools like Retool or custom Vue dashboards – while staying 100% in Laravel.

If you’re still creating dummy Eloquent models just to satisfy Filament tables in 2025, you’re doing it wrong.

Try it today. Drop a model-less table into your next project and watch how much cleaner (and faster) your code becomes.

Have you already used custom data sources in production? What’s your favorite use case? Drop it in the comments – the Filament community is eating this stuff up right now.