Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Reports Laravel Package

eightynine/filament-reports

Filament Reports brings report-building to Filament admin panels. Define report classes with query-based data, columns, filters, and actions, then give teams a dedicated page to view and export business data without building custom dashboards from scratch.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer and register the plugin on the panel you want reports in. Match the package major to your Filament major — 4.x targets Filament v4, 3.x targets Filament v3:

# Filament v4 panel
composer require eightynine/filament-reports:^4.0
# Filament v3 panel
composer require eightynine/filament-reports:^3.1

php artisan vendor:publish --tag="filament-reports-config"   # optional, but do it before you customize
// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(\Eightynine\FilamentReports\FilamentReportsPlugin::make());
}

Where to look first:

  1. The published config/filament-reports.php — it controls default pagination, export behavior, and the disk/format used for exported files. Skim it before writing any report.
  2. The base Report class shipped by the package — this is the contract every report extends. Read its abstract/stub methods; they tell you exactly what a report is made of (query, columns, filters).
  3. The package README examples — the fastest path to a working report is copying the smallest example and swapping the model.

First use case — a "Revenue by month" report. Generate the skeleton with the package's artisan command (typically php artisan make:filament-report RevenueReport), then fill in the query and columns:

class RevenueReport extends Report
{
    protected function query(): Builder
    {
        return Order::query()
            ->selectRaw('DATE_FORMAT(created_at, "%Y-%m") as month, SUM(total) as revenue, COUNT(*) as orders')
            ->groupBy('month')
            ->orderByDesc('month');
    }

    protected function columns(): array
    {
        return [
            TextColumn::make('month')->label('Month')->sortable(),
            TextColumn::make('revenue')->money('usd'),
            TextColumn::make('orders')->numeric(),
        ];
    }
}

Register it wherever your panel exposes pages (a Reports page or a widget), open the panel, and confirm the table renders before adding filters or exports. Get the "happy path" working first — filters and export actions are where most debugging time goes.

Implementation Patterns

Report as a first-class class. Keep each report in its own class under app/Filament/Reports. Treat the query as the source of truth and the columns as presentation only. This makes reports reusable across a dedicated page, a dashboard widget, and a scheduled email job without duplication.

Query-first, columns-second. Build and test the raw query in Tinker or a feature test before wiring it into the report. The report layer is thin; almost every bug is a SQL bug (wrong join, missing groupBy, filter applied to the wrong table alias).

Filters as composable state. Define filters (date range, select, ternary) on the report class and let the package bind them into the query. A common pattern is a shared DateRangeFilter applied to created_at, with a SelectFilter on status:

protected function filters(): array
{
    return [
        Filter::make('period')->form([DatePicker::make('from'), DatePicker::make('until')])
            ->query(fn (Builder $q, array $data) => $q
                ->when($data['from'], fn ($q, $d) => $q->whereDate('created_at', '>=', $d))
                ->when($data['until'], fn ($q, $d) => $q->whereDate('created_at', '<=', $d))),
        SelectFilter::make('status')->options(OrderStatus::class),
    ];
}

Embedding three ways. Reports typically surface as (a) a dedicated Filament page in the nav, (b) a widget on a dashboard, or (c) a tab inside a resource page (e.g., a "Reports" tab on the Customer resource scoped to customer_id). Build the report so the query accepts a scope (tenant ID, customer ID) via a constructor or settable property — this is what makes one report serve all three contexts.

Exports as actions, not a separate feature. Wire CSV/Excel export into the report's header actions rather than building a separate controller. The package's export action already respects the current filter state, which is the behavior users expect ("export what I'm looking at"). If you need scheduled exports, reuse the same report class from a queued console command and ->toCsv() on the query — do not re-implement the column mapping.

Authorization. Gate report visibility with Filament's canView() / policies and, for multi-tenant panels, scope every query by tenant_id in the base query — filters must not be able to widen that scope.

Testing. Write one feature test per report that hits the query builder with known fixtures and asserts row counts and aggregate values. Asserting the rendered Filament table is brittle; asserting the query is not.

Gotchas and Tips

  • Filament v3 vs v4 — pin the major. The 4.0.0 release added Filament v4 support (3.x remains the v3 line). Check the version constraint in composer.json before upgrading either side: upgrading Filament to v4 on a filament-reports:^3.1 constraint will silently leave the package on a Filament v3-only major and break widget/page registration before it breaks your reports. Align majors: Filament v3 → package 3.x, Filament v4 → package 4.x.
  • 4.0.0 is a major bump — treat it as breaking until proven otherwise. The release note is a single line ("Adds support for filament v4"), and the full changelog diff is the only way to confirm whether v3 public APIs (base Report, filter/column contracts, plugin registration) shifted. Read the diff before dropping 4.0.0 into an existing panel that has custom reports or subclasses.
  • Aggregates + filters collide. If you groupBy and also filter on a column, MySQL's ONLY_FULL_GROUP_BY will reject the query. Always select every non-aggregated column you filter or order by, or use ->selectRaw() consistently.
  • Column name collisions after joins. Joining customers onto orders gives you two id and two created_at columns. Alias everything (orders.created_at as ordered_at) — Filament columns reference the alias, and a silent collision produces wrong values with no error.
  • N+1 in rendered rows. The report table re-queries relationships per row. Add ->with([...]) in the base query for any relationship-backed column; it is the single biggest performance win.
  • Export limits on large datasets. Default exports can attempt to hydrate everything and time out. Check the config for a chunk/limit setting and prefer a queued export for anything over a few thousand rows.
  • Cache keys must include filter state. If you cache report results, hash the serialized filter payload into the key. A cache key built only from the report class name will serve stale data after a filter change and is a classic "why is my report wrong" bug.
  • Debugging empty reports. When a report renders zero rows, dd($report->query()->toSql()) and run it manually. Nine times out of ten it's a filter that binds null into a where, or a DatePicker returning a string the query casts incorrectly. Filament's ->when() pattern (shown above) avoids most of this.
  • Config publishing order. Publish the config once at install. If you publish after customizing, you'll overwrite changes silently. Commit the config to version control. Re-check the published config after the 4.0.0 upgrade — a major that adds a Filament major can add new config keys that won't appear until you re-publish or diff upstream.
  • Timezone in date filters. whereDate uses the DB timezone while DatePicker uses the app timezone. For reports spanning midnight, store UTC and convert explicitly, or users will report "missing" rows from the first hours of the day.
  • Extension points. Before editing vendor files, check whether the report stub/view can be overridden via the published config or by extending the base Report class. Custom column types are best added as your own Filament column classes rather than monkey-patching the package.
  • Actively maintained but young. With 101 stars and a small dependent count, the project is still finding its footing — 4.0.0 arriving as a bare "add support for Filament v4" major is a reminder to pin versions (^4.0, not *) and read the changelog diff before every minor bump.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource