Doc Studio plugin screenshot
Dark mode ready
Multilingual support
Supports v5.x

Doc Studio

Community

A Filament Builder field that lets end users design their own PDF templates — merge fields and line item tables included — without a developer touching a Blade file.

Tags: Forms Form Editor Field Form Field Form Layout
Supported versions:
5.x 4.x
Third-party plugin. This is built by the community, not the Filament team. Filament does not review, endorse, or vet the security of plugins outside the filament/ namespace. Review the source and install at your own risk. Found malware or an unresolved security issue the author won't address? Report it .
Tommaso Musetti avatar Author: Tommaso Musetti

Documentation

Tests Latest Version License

A PDF template builder for end users, inside Filament. Your client drags blocks into a template and prints a record as a PDF — without a developer touching a Blade file for every "can you change the quote layout?".

Building a template: name and model, a paragraph block with a merge field, a table bound to a data source collection, all reflected live in the preview

v1.0.0. The full v1 scope is out: 3 blocks, merge fields, a live preview, dompdf rendering. Feedback and issues are welcome.

#Status

Editor Filament Builder field, inside your panel
Blocks heading, paragraph (with merge fields), table (line items)
Merge fields yes — a whitelist you declare per model
Preview live, next to the editor, rendered against a sample record
Engine dompdf (pure PHP, nothing to install on the server)

#Installation

composer require tommasomusetti/filament-doc-studio
php artisan doc-studio:install

The install command publishes the migration and offers to run it. By hand:

php artisan vendor:publish --tag="doc-studio-migrations"
php artisan migrate

Then add the plugin to the panel that should get the editor:

use TommasoMusetti\DocStudio\DocStudioPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugins([
            DocStudioPlugin::make(),
        ]);
}

Templates now live under Document templates in that panel.

#Rendering a PDF

use TommasoMusetti\DocStudio\DocumentRenderer;
use TommasoMusetti\DocStudio\Models\DocumentTemplate;

$template = DocumentTemplate::where('slug', 'quote')->firstOrFail();

$pdf = app(DocumentRenderer::class)->pdf($template); // raw PDF bytes

The renderer never touches Filament, so this works from a queued job or a console command as well as from a panel.

#Whitelisting merge fields and line item tables

The paragraph and table blocks pull data from a model, but only through a DocumentDataSource you write — a template can never reach an Eloquent attribute you didn't explicitly expose:

use TommasoMusetti\DocStudio\Contracts\DocumentDataSource;

class OrderDataSource implements DocumentDataSource
{
    public static function model(): string
    {
        return Order::class;
    }

    public function fields(): array
    {
        return [
            'customer_name' => [
                'label' => 'Customer name',
                'resolver' => fn (Order $order): string => $order->customer->name,
            ],
        ];
    }

    public function collections(): array
    {
        return [
            'line_items' => [
                'label' => 'Line items',
                'columns' => ['name' => 'Item', 'qty' => 'Qty'],
                'resolver' => fn (Order $order): iterable => $order->lines
                    ->map(fn ($line) => ['name' => $line->name, 'qty' => $line->qty]),
            ],
        ];
    }

    public function sample(): Order
    {
        // The record the live preview renders against while editing.
        return Order::query()->latest()->first() ?? new Order(['id' => 0]);
    }
}

Register it once, in a service provider's boot():

app(DocumentRenderer::class)->registerDataSource(OrderDataSource::class);

The paragraph block's merge tag picker and the table block's collection picker both read this whitelist — a field or column disappears from the editor the moment you stop declaring it, without touching saved templates.

#Restricting the blocks a panel offers

DocStudioPlugin::make()->blocks([
    HeadingBlock::class,
]);

A panel can only narrow the list. It cannot offer a block the renderer does not know: disabling a block must never break templates already saved with it.

#Adding your own block

A block is one class with two faces — an editor field set, and print HTML:

use Filament\Forms\Components\Builder\Block;
use Filament\Forms\Components\TextInput;
use TommasoMusetti\DocStudio\Blocks\DocumentBlock;
use TommasoMusetti\DocStudio\RenderContext;

class StampBlock extends DocumentBlock
{
    public static function make(): Block
    {
        return Block::make('stamp')->schema([
            TextInput::make('text')->required(),
        ]);
    }

    public function render(array $data, RenderContext $context): string
    {
        return '<p class="stamp">' . e($data['text'] ?? '') . '</p>';
    }
}

Register it with the renderer, in a service provider's boot():

app(DocumentRenderer::class)->register(StampBlock::class);

Write the HTML dompdf first: table layouts, conservative CSS, no flexbox or grid. And escape anything the user typed — a template is user written content.

#Local development

workbench/ is a small Laravel app with a real Filament panel, so the plugin can be opened in a browser:

composer serve   # http://127.0.0.1:8000/admin

Log in with test@example.com / password. The same panel is registered in the test suite, so the panel tests run against what you see in the browser.

composer test
composer analyse
composer lint

#Contributing

Please see CONTRIBUTING.

#Security

Please review our security policy on how to report security vulnerabilities.

#Credits

#License

The MIT License (MIT). Please see License File for more information.

The author

Tommaso Musetti avatar Author: Tommaso Musetti

Tommaso Musetti is a software engineer working as a developer, and a freelance web developer for businesses on the side. He builds small, focused Laravel and Filament tools and open source plugins in his spare time.

Plugins
1
Stars
1