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 Record Pdf Laravel Package

amjadiqbal/filament-record-pdf

Filament Record PDF adds PDF export to Filament resources, letting you generate and download a PDF for any Eloquent record from the admin panel. Configure Blade templates and options to customize the output, then trigger it via a table or header action.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and generate your first view in one pass:

composer require amjadiqbal/filament-record-pdf
php artisan make:record-pdf-view Invoice

That command scaffolds the Blade view the package renders per resource — this is the file you will spend 90% of your time in, so open it first. It ships a working example of the built-in "visible attributes" layout plus an invoice preset you can copy from. Look at the generated view before touching any action configuration: the action options only control how the PDF is produced, the view controls what it looks like.

Then attach an action to a resource. The canonical first use case is a download button on a table row or the record's view page:

use Amjadiqbal\FilamentRecordPdf\Actions\DownloadPdfAction;
use Amjadiqbal\FilamentRecordPdf\Actions\PreviewPdfAction;

// In your Resource's table() or the ViewRecord page header actions
->actions([
    PreviewPdfAction::make(),
    DownloadPdfAction::make(),
])

Preview first, download second — preview caches the rendered file for 10 minutes and the download button reuses it, so iterating on your Blade view while clicking Preview is noticeably faster than clicking Download each time.

Minimum working setup is three things: the package installed, a view resolved via pdfView(), and a policy method (or the view/update fallback) allowing the user through. If the button renders but 403s, it is almost always the policy, not the view.

Implementation Patterns

One view per resource, shared partials for chrome. The package resolves a view per resource; keep the per-resource view thin (a table of attributes or an invoice body) and put headers, footers, logos, and page CSS in a shared partial included by each. This keeps the make:record-pdf-view output as a starting point rather than a copy-paste sprawl.

Compose actions by intent rather than one mega-action. A common header layout:

->headerActions([
    PreviewPdfAction::make()->pdfView('pdf.invoice'),
    DownloadPdfAction::make()->pdfFilename(fn ($record) => "invoice-{$record->number}"),
    EmailPdfAction::make()->queue(),          // optional queueing
    SavePdfAction::make()
        ->disk('archive')
        ->directory('invoices/2026')
        ->overwrite(false),                   // keep prior versions
])

SavePdfAction also writes into a column on the record or into a spatie/laravel-medialibrary collection — use the column variant when downstream code (mailers, API responses, other admin panels) needs the file path, and medialibrary when you want conversions, thumbnails, and the media UI for free.

Pick the driver per action, not globally. dompdf is the default and is pure PHP (no Node, no Chrome, no external service) — great for text-heavy invoices and fast enough for bulk. Switch to Browsershot/Gotenberg/Cloudflare/WeasyPrint via pdfDriver() on the specific action that needs real CSS (flexbox, grid, web fonts, modern layout). You can leave the global config on dompdf and only upgrade the one fancy report.

Use pdfData() for computed values, not for queries. Anything in pdfData() runs on every render including the bulk path; pass precomputed totals, formatted dates, and resolved relations. Keep pdfView() dumb.

Bulk downloads: design for the queued path from day one. Above 25 records (configurable) the ZIP is built on the queue and the user receives a database notification with a signed, user-bound, expiring link instead of a direct download. That means your local and staging environments need a running queue worker and the notifications table migrated, or the button will appear to do nothing for large selections. Test with 30+ selected rows, not 3, so you exercise the branch you will actually hit in production.

Authorization: implement the specific abilities. The package checks viewPdf / downloadPdf / emailPdf / savePdf and falls back to view or update. Start with the fallback (it just works), then split them out as soon as you have a role that should read but not email, or download but not save. pdfAbility() and authorizePdfUsing() are the escape hatches when a policy method is too coarse or the check is not record-scoped.

Customisation hooks for the last mile. modifyPdfUsing() is where watermarks, per-tenant footers, and "DRAFT" stamps belong — it gets the generated PDF so you do not fork the view. pdfFormat() / pdfLandscape() handle the boring but frequently requested A4-vs-Letter and orientation toggles; expose them as a per-record setting rather than hardcoding.

Email as a workflow, not a one-off. EmailPdfAction collects To (up to 10 addresses), Subject, and Message in a modal, attaches the PDF, and supports sendUsing() for a custom Mailer/Mailable. On any tenant-facing app, queue it — synchronous sending inside a Livewire request will tie up a worker slot and time out on slow SMTP.

Gotchas and Tips

dompdf version floor is a security boundary, not a suggestion. The package requires dompdf 3.1.6+; earlier 3.x releases carry six 2026 advisories including an SVG local-file read. If your app pins dompdf directly elsewhere, run composer why dompdf and confirm you are not being downgraded by another dependency. Check composer.lock after install, not just the install output.

dompdf will not render your Tailwind layout. No flexbox, no grid, limited web fonts, no position: fixed headers the way a browser does it. The built-in view is deliberately plain for this reason. Budget real time for the first "why does the preview look fine in the modal but the PDF is wrong" bug — and remember the modal preview is the PDF, so what you see there is the truth.

Preview caching is 10 minutes and user-bound. The signed URL only opens for the requesting user, so copy-pasting it to a colleague fails by design, and a PDF regenerated after a record edit may still serve the cached file for up to 10 minutes. During development, clear the cache between iterations (php artisan cache:clear, or whatever store the package uses) instead of assuming your view changes are broken.

Filename and directory sanitisation is aggressive and intentional. pdfFilename() values are reduced to [a-z0-9-] plus .pdf, and directories must be relative with no ./... So "Invoice #123 (Final).pdf" becomes something much plainer. Do not fight it or bypass it — if you need expressive names for humans, keep the sanitised file and put the pretty name in the notification or the medialibrary name/custom_properties.

overwrite(false) grows your disk silently. It is the right default for audit trails, but nothing prunes old versions. Pair it with a scheduled cleanup or a retention policy on the disk, otherwise invoices/2026/ becomes a disk-full incident in eighteen months.

Queued bulk jobs need three things wired before they work: a running queue worker, the notifications table migrated (the link arrives as a database notification), and APP_URL set correctly — the signed expiring link is generated from it, so a wrong APP_URL produces a notification containing an unreachable host. This is the single most common "the package is broken" report.

Bulk authorization is per record. Selecting 40 rows where the user can see 35 yields a ZIP of 35 — verify that is the behaviour you want and surface it in the UI, because users assume the count in the toolbar equals the count of PDFs.

Driver-specific failure modes. Browsershot needs Node/Chrome available to the PHP process (a which node that works in your shell may not work under php-fpm). Gotenberg/Cloudflare/WeasyPrint need network access from the worker, so they break in air-gapped or egress-restricted environments where dompdf just works. If PDFs must work everywhere with zero infrastructure, stay on dompdf and accept the CSS limits.

Medialibrary and column targets add a dependency edge. Only enable those options if spatie/laravel-medialibrary (or the target column) is actually present and migrated — otherwise SavePdfAction fails at runtime rather than at boot. Prefer the plain disk+directory variant for the first integration, then add the medialibrary collection once you have confirmed the render works.

Testing. The package's own suite runs Pint + PHPStan level 5 + 67 Pest tests across PHP 8.2–8.5, Laravel 12/13, Filament 4/5, plus a 44-check headless-Chrome UI test — useful as a template. For your app, assert on the action (button visible, policy denies for a low-privilege user, queued bulk dispatches a job) rather than on PDF bytes; byte-level PDF assertions are brittle across driver and dompdf patch versions. If you need content assertions, render the Blade view directly and test the HTML.

Extension points worth knowing before you fork anything: modifyPdfUsing() (post-process the file), authorizePdfUsing() / pdfAbility() (authorization), pdfData() (computed context), sendUsing() (mail transport), and per-action pdfDriver(). Between those five you can cover watermarks, tenant scoping, custom mailers, and mixed-fidelity output without touching package internals.

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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk