Routing & invocation
A report is addressed by its key(). The package registers the filter form, preview, document and PDF as separate routes — link them or invoke them directly from code.
The report routes
All routes use the prefix reports by default. The route names stay the same when you change the prefix.
| GET /reports/{name} · reports.form | Filter form. If the report has no filter controls, the route redirects straight to the preview. |
|---|---|
| GET /reports/{name}/run · reports.preview | Preview page with the frame, zoom, print, PDF and “change filters” link. Filters arrive as query parameters. |
| GET /reports/internal/{name} · reports.internal | Only the report document as HTML — for your own iframe or an embed without the preview chrome. |
| GET /reports/{name}/pdf · reports.pdf | Streams the PDF with the same filters. |
| GET /reports/assets/{file} · reports.asset | Shared CSS file for the form and the preview. The UI components include it automatically. |
Form first — or straight to the preview
The link to /reports/{name} shows the filter form first. If there are no filters (empty filterControls()), the route redirects automatically to /reports/{name}/run. To skip the form, link the preview route directly.
The flow
The report is only executed once the filters are fixed. The preview is just the shell; the actual document lives on the internal route.
reports.form
Choose filters
The user fills in the form. The values become query parameters on the form target.
reports.preview
Preview
The route validates the filters and renders the frame. The iframe loads the document from reports.internal.
reports.internal · reports.pdf
Document or PDF
Same filters, two outputs: HTML for embedding or a streamed PDF.
Generating links
Use the route names instead of hard-coded paths. That keeps you independent of the configured prefix.
{{-- 1. Filter form first: the user chooses filters, then continues. --}}
<a href="{{ route('reports.form', ['name' => 'price_list']) }}">Preisliste öffnen</a>
{{-- 2. Direct preview, with filters in the query string. --}}
@php($filters = [
'category' => 'Küche',
'price' => ['operator' => '>=', 'value' => [50]],
])
<a href="{{ route('reports.preview', ['name' => 'price_list']).'?'.http_build_query($filters) }}">Vorschau</a>
{{-- 3. The raw report document, e.g. for your own iframe. --}}
<iframe src="{{ route('reports.internal', ['name' => 'price_list']).'?'.http_build_query($filters) }}"></iframe>
{{-- 4. Streamed PDF with the same filters. --}}
<a href="{{ route('reports.pdf', ['name' => 'price_list']).'?'.http_build_query($filters) }}">PDF</a>
Four typical links: form, preview, embedded document and PDF.
Careful: a filter field called “name”
The route has a {name} parameter for the report key. A filter field keyed name would overwrite the report key with route(..., ["name" => ...] + $filters). Always append filters as their own query string: route(...)."?".http_build_query($filters). The package routes do the same internally.
Filters as query parameters
The package routes accept the same shapes the automatic form produces. Unknown keys are dropped.
| ?category=Küche | Single value. The default operator of the control is used. |
|---|---|
| ?category[]=Küche&category[]=Büro | Multiple values for multiselect and picker. |
| ?price[operator]=>=&price[value][]=50 | Explicit operator with one value. |
| ?price[operator]=between&price[value][]=50&price[value][]=100 | Range with two values. |
| ?name[operator]=like&name[value]=Kaffee | Text filter. Without an operator, like is used and the value is wrapped in %. |
| ?foo=bar | Unknown keys are silently dropped by the FilterValidator. |
Invoking from code
The Reports facade accepts the same report key and filter arrays. html() returns the complete document as a string; pdf() returns a fluent PDF builder.
| Reports::html($report, $filters = []) | Complete HTML document as a string — ideal for your own controllers or embeds. |
|---|---|
| Reports::render($report, $filters = []) | Same pipeline; the configured renderer determines the return type. |
| Reports::pdf($report, $filters = []) | PdfBuilder with stream(), download(), save(), name() and disk(). |
| Reports::filterControls($report) | A list of FilterControl objects for your own filter UI. |
| Reports::filterSchema($report) | The same controls as JSON-ready arrays. |
| Reports::find($key) | Resolve a report instance by its key(). |
| Reports::all() · register() · resolve() | Read the registry, register reports or pass an instance through. |
| Reports::definition($report) · pageSetup($report) | The resolved ReportDefinition and effective page setup. |
use Guggach\Reports\Facades\Reports;
// Full HTML document (layout + bands) as a string.
$html = Reports::html('price_list', ['category' => 'Küche']);
// Same pipeline; the configured renderer decides the return type.
$result = Reports::render('price_list', ['category' => 'Küche']);
// PDF: stream, download or save.
return Reports::pdf('price_list', ['category' => 'Küche'])
->name('preisliste.pdf')
->download();
// Filter metadata for a custom UI.
$controls = Reports::filterControls('price_list'); // FilterControl[]
$schema = Reports::filterSchema('price_list'); // array, JSON-ready
use Guggach\Reports\Facades\Reports;
use Illuminate\Http\Response;
final class PriceListController
{
public function __invoke(): Response
{
return response(
Reports::html('price_list', ['category' => 'Küche'])
);
}
}
Render a report from a controller or deliver it as a PDF.
Prefix, middleware and protection
The routes are enabled by default and run in the web middleware group. Add authentication or a policy for production.
'routes' => [
'enabled' => true,
'prefix' => 'reports',
// Add your own middleware, e.g. auth or a report policy.
'middleware' => ['web', 'auth'],
],
Adjust routing in config/reports.php.
reports.routes.prefix changes only the path, not the route names (reports.form, reports.preview, reports.internal, reports.pdf, reports.asset). Setting reports.routes.enabled = false disables the routes entirely; then call Reports::html()/pdf() from your own controllers.