jimmyjs/laravel-report-generator
Quickly generate PDF, Excel, and CSV reports in Laravel from any query builder or Eloquent query. Define columns, grouping, and aggregates in PHP, render with Blade views, and export or stream polished reports through a simple, fluent API.
Install the wrapper and its drivers. The package itself is thin — it orchestrates barryvdh/laravel-dompdf (or laravel-snappy) and maatwebsite/excel, so you need to install the driver you intend to use, then the package:
composer require barryvdh/laravel-dompdf maatwebsite/excel
composer require jimmyjs/laravel-report-generator
The service provider is auto-discovered. Publish the config (and views if you plan to restyle PDFs):
php artisan vendor:publish --tag=report-config
Where to look first. Read config/report-generator.php to see the default PDF/Excel settings (paper size, orientation, view templates), then skim src/ReportGenerator.php — the whole public surface is essentially of(), editColumn(), setMeta(), showHeader(), download(), show(). The package is small enough to fully understand in ~20 minutes.
First use case. The canonical day-one report is an admin "export filtered list" button. Build your query in the controller, define a header→column map, and hand it off:
use Jimmyjs\ReportGenerator\Facades\ReportGenerator;
public function users()
{
$query = User::query()->latest();
$columns = [
'Name' => 'name',
'Email' => 'email',
'Created At' => 'created_at',
];
return ReportGenerator::PdfReport()
->of('User Report', $columns, $query)
->showHeader()
->download('users-' . now()->format('Y-m-d'));
}
Swap PdfReport() for ExcelReport() and the same query/columns produce a spreadsheet. That symmetry is the core value proposition — you write the query and column map once.
One query, two media. The highest-leverage pattern is a private method returning [$columns, $query], then two thin controller actions (exportPdf, exportExcel) that differ only in the media call. This keeps filter logic in one place and prevents PDF/Excel drift. Pass the same request filters into the query builder:
private function userQuery(Request $request)
{
return User::query()
->when($request->role, fn ($q, $r) => $q->where('role', $r))
->when($request->from, fn ($q, $d) => $q->whereDate('created_at', '>=', $d));
}
Column maps do the heavy lifting. Keys are the human-readable headers, values are the model attribute or accessor. For anything that isn't a plain string, reach for editColumn() with a closure — this is where you format dates, currency, booleans, and relations:
->editColumn('created_at', fn ($row) => $row->created_at->format('d M Y'))
->editColumn('status', fn ($row) => ucfirst($row->status))
Meta describes the report, not the data. Use setMeta() to inject the filter set (date range, role, generated-by) into the report header so the PDF is self-explanatory when someone forwards it. This is what turns a data dump into a report stakeholders trust.
Header/footer and orientation are per-report. showHeader() / setOrientation('landscape') / setPaper('a4') are chainable, so wide tables (many columns) get landscape while summary reports stay portrait. Configure the PDF view template once in the published config, not per call.
Encapsulate reusable reports. For anything used more than twice, extract a small report class per domain (e.g. App\Reports\UserReport) with a build(Request $request): array returning query+columns. Controllers stay thin and the report becomes unit-testable.
Queue large exports. For anything above a few thousand rows, generate in a job, Storage::put() the file, and email or notify the user with a signed download URL. Synchronous PDF generation in a request is the #1 cause of timeouts with this package.
DomPDF is the bottleneck, not the package. Rendering 5k+ rows as HTML into PDF will blow memory or hit max_execution_time. Keep PDF reports summarised (aggregates, top-N, paginated sections) and push full exports to Excel. Raise memory_limit cautiously; chunk your query rather than the PDF render.
Pin your Excel version. The package was built against maatwebsite/excel 3.x. If you're on a newer Laravel with a different Excel major, install the driver version the package declares in composer.json first, or the export will fail at runtime with a class-not-found in the writer pipeline.
Carbon and enums don't auto-format. Raw created_at values will render as 2024-01-15T00:00:00.000000Z in the PDF. Always editColumn date columns. Same for enums — cast them to ->value or a label accessor.
select() breaks eager-loaded accessors. If your column map references $row->relation->name, use with('relation') and avoid restrictive select() calls that drop the foreign key. SQLite/MySQL will silently return null and your PDF will look empty.
Filename sanitisation is on you. download($name) passes through to the response header; strip slashes, colons, and non-ASCII from user-supplied names or downloads fail on Windows clients.
Publish views before customising. Overriding the PDF Blade template without publishing gives you a confusing "view not found" — publish with vendor:publish --tag=report-views (check the exact tag in the README) and edit in resources/views/vendor/.
Version risk. Last release was April 2022 and it targets Laravel 5-era APIs. On Laravel 10/11, test the facade binding and the dompdf/excel dependency resolution before committing — if it fights Composer, either pin to a compatible Laravel or vendor the ~200 lines of orchestration into your own App\Support\Reports layer. The package is simple enough to fork; treat it as a reference implementation as much as a dependency.
Testing tip. Assert on Content-Type (application/pdf, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) and Content-Disposition rather than parsing the binary. For content assertions, test the query/column-map builder method directly — that's where the logic lives.
How can I help you explore Laravel packages today?