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 Docs Laravel Package

tomatophp/filament-docs

Manage documents and contracts in one place with a template builder. Generate docs from templates using the Tiptop editor, add custom variables via facade, filter by template, and print or export as PDF with custom headers, footers, and CSS.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the version gate before anything else. v5.0.0 targets Filament v5 + Laravel 12/13. The Filament v3 line lives on the v3 branch. There is no supported Filament v4 line — if the panel is on v4 you must upgrade to v5 or accept an unsupported island. This is a ten-minute check that saves a week of estimation.

composer require tomatophp/filament-docs
php artisan filament-docs:install

Register the plugin in the panel provider (skip only if you intend to use the action/relation manager standalone):

->plugin(\TomatoPHP\FilamentDocs\FilamentDocsPlugin::make())

Where to look first, in this order:

  1. config/filament-docs.php — publish it immediately (--tag="filament-docs-config") and read it end to end. It carries the displayname_attribute and team_model keys that bite later.
  2. The DocsVar contract and the FilamentDocs facade — this is the entire extension surface for data injection.
  3. DocumentAction — the piece that touches your existing resources.
  4. The published Blade views (--tag="filament-docs-views") — the print page is where every real requirement lands (header, footer, page breaks, branding).

First use case, end to end. Pick one low-stakes document type (an invoice copy, a welcome letter — not a contract). Register a global var, build a template, drop the action on an existing resource table, generate, print.

// AppServiceProvider::boot()
use TomatoPHP\FilamentDocs\Facades\FilamentDocs;
use TomatoPHP\FilamentDocs\Services\Contracts\DocsVar;

FilamentDocs::register([
    DocsVar::make('$COMPANY_NAME')->label('Company Name')->value(config('app.name')),
    DocsVar::make('$TODAY')->label('Today')->value(fn () => now()->translatedFormat('d M Y')),
]);
// In the resource table
use TomatoPHP\FilamentDocs\Filament\Actions\DocumentAction;

DocumentAction::make()
    ->vars(fn ($record) => [
        DocsVar::make('$CUSTOMER_NAME')->value($record->name),
        DocsVar::make('$CUSTOMER_EMAIL')->value($record->email),
    ])

Then author a template containing $CUSTOMER_NAME and $COMPANY_NAME, and hit generate from the row action. If that loop closes in a branch in half a day, the package is viable for you. If the printed PDF looks wrong here, it will look wrong everywhere — fix it now.

Spike checklist before committing: migrations run clean on a copy of production schema; panel renders with the plugin registered; a contract-shaped template (multi-page, logo, signature block, repeating header/footer) renders acceptably through whichever PDF driver you land on; your Filament version is v5 or you've accepted the v3 branch.

Implementation Patterns

Keep the variable vocabulary in one place. The single biggest day-to-day failure mode is drift: a developer renames a var in PHP, the template still says $CUSTOMER_ADDRESS, and the printed document silently shows the literal token. Centralise the names.

final class DocVars
{
    public const CUSTOMER_NAME  = '$CUSTOMER_NAME';
    public const CUSTOMER_EMAIL = '$CUSTOMER_EMAIL';
    public const ISSUE_DATE     = '$ISSUE_DATE';
}

Use the constants in both register() and ->vars(), and keep a human-readable list of available tokens beside the template editor (a static page in the panel or a README in the repo) so whoever authors templates isn't guessing.

Two tiers of variables, used deliberately.

  • Global (FilamentDocs::register) for values that are stable across documents: company details, legal boilerplate, locale-formatted dates, current user. Register these once in a dedicated DocsServiceProvider — not in AppServiceProvider, which grows into a junk drawer.
  • Per-record (DocumentAction::vars()) for anything that belongs to the row you clicked. Always pass a closure (fn ($record) => [...]) so values are resolved at click time, not at table-render time.

Mix them: globals for the letterhead, per-record for the body. The package merges them at generation.

Use model() + column() for lookup-style vars, value() for computed ones.

DocsVar::make('$POST_TITLE')->label('Post Title')->model(Post::class)->column('title'),
DocsVar::make('$SELECTED_TIME')->label('Selected Time')->value(fn () => now()->subDays(10)->translatedFormat('D-M-Y')),

model()/column() is fine for pickers and reference data, but it's a lookup against a table — don't reach for it when a closure over $record is simpler and more direct.

Wrap documents into existing resources with the relation manager. For a Customer resource, add documents as a relation manager so the whole lifecycle (list, generate, print, filter by template) sits inside the record page. This is where the package earns its keep day to day — sales or ops staff never leave the customer view.

Branding lives in the service provider, not in forked views.

public function boot(): void
{
    FilamentDocs::header('filament.docs.header');
    FilamentDocs::footer('filament.docs.footer');
    FilamentDocs::css('filament.docs.print');
}

Treat this triple as configuration. Because the print stylesheet is bundled (v5 replaced the Tailwind CDN with local CSS), your override file is the only reliable place to control page size, margins, and page-break behaviour — and it survives package upgrades, unlike editing the published view in place.

Tenancy: opt in consciously, and own the migration. ->isScopedToTenant() alone does not add team_id; you write the migration for both documents and document_templates. Do it at install time, not after you have production rows — backfilling tenant IDs into a documents table is genuinely unpleasant, and a null team_id on a scoped model is a silent isolation hole. Add the column, an index, and a non-null constraint (or a strict policy) in the same PR.

Template lifecycle: one template per document type, versioned by name. Author templates in the panel, snapshot the HTML into seeders/fixtures so a fresh environment has the same set. Because v5 sanitises on save and on render, do not treat the stored HTML as canonical source — keep your authored templates in version control somewhere.

Testing. v5 ships model factories and a test suite covering resources, print page, actions, relation manager and install command. Lean on the factories to assert your own var wiring:

$doc = Document::factory()->create();
$this->actingAs($user)
     ->get(DocumentPrintUrl::make($doc))
     ->assertSee($customer->name);

That's your regression net for the sanitizer and print-CSS behaviour across upgrades.

Gotchas and Tips

Sanitization is the trap that costs the most. HTML is sanitised on save and on render. Inline styles, custom classes, embedded fonts, iframes, and data-* attributes may be stripped — and because it happens on save, you lose the original. Branded contract output that relies on any of these will degrade, quietly. Audit an actual template against the sanitizer before sign-off, and maintain an allowlist decision in writing. If you need something the sanitizer forbids, that's a fork or an override, not a workaround.

Tiptap-era templates will not survive the RichEditor move. v5 removed Tiptap in favour of Filament's RichEditor. If you have templates authored under v1/v2, assume re-authoring, not conversion. Budget for it explicitly and keep exported copies of the old HTML for reference.

Missing variables fail silently. Substitution is text replacement; an unmatched token typically renders literally in the document. Guard it: render a preview, or assert in tests that generated output contains no $ tokens matching /\\$[A-Z_]+/. Cheaper than a client finding $CUSTOMER_ADDRESS on a signed page.

The PDF driver is your dependency, not the package's. Font support, page-break fidelity, repeating headers/footers, image handling, and render cost all live below this package. Validate against a real multi-page contract with a logo and a signature block. Do not sign off on a hello-world. Header/footer repetition across page breaks in particular is driver-specific and the most common "it works but looks wrong" complaint.

Print CSS: expect to override, and pin the override. The bundled print stylesheet is a feature (no network call at print time, good for locked-down environments) but it also means you inherit it. Publish the views, keep your customisations in your own CSS file passed through FilamentDocs::css(), and re-check after every upgrade — print layout is not covered by a "does it boot" smoke test.

Version pin and assume fork-ability. Single maintainer, ~45 stars, 0 dependents, and a v1→v2→v5 history with breaking changes in storage format, editor integration, and config keys. Pin a minor version, read release notes before bumping, and remember MIT + a small surface area + publishable Blade views means vendoring is a real fallback. That's cheap insurance, not paranoia.

Escape-awareness on the print action. v5 escapes the title and URL passed to the print actions' JavaScript — a real fix, but it means any prior trick of injecting HTML into action labels stops working. Use plain strings.

Debugging moves that pay off: publish the views (--tag="filament-docs-views") and read the print Blade rather than guessing; publish and read the config before touching the DB; use the shipped factories to reproduce a failing render in a test rather than in the browser.

Know where the package stops. It authorises, generates, and prints. It does not provide e-signature, approval workflows, immutable versioning, an audit trail, retention policy, or document-type classification. For anything contract-critical, build on top: snapshot the rendered HTML into your own immutable table at generation time, log generation/print events, and add per-document authorization policies — relation managers and template filters are not an authorization boundary, and cross-tenant leakage through those paths is your responsibility to test.

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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table