tapp / filament-certificate-builder
Filament plugin for designing and rendering certificate templates
Package info
github.com/TappNetwork/filament-certificate-builder
pkg:composer/tapp/filament-certificate-builder
Fund package maintenance!
Requires
- php: ^8.3
- filament/filament: ^5.0
- filament/spatie-laravel-media-library-plugin: ^5.0
- spatie/browsershot: ^5.0
- spatie/laravel-medialibrary: ^11.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-livewire: ^4.0
- rector/rector: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-06 22:24:15 UTC
README
A Filament plugin for designing certificate templates and rendering them as HTML or PDF. Host apps register token sets and a resolver; the package never imports your domain models.
Installation
composer require tapp/filament-certificate-builder
Register the plugin on your Filament panel:
use Tapp\FilamentCertificateBuilder\FilamentCertificateBuilderPlugin; $panel->plugin(FilamentCertificateBuilderPlugin::make());
If you have not set up a custom theme, follow the Filament theme docs first, then add:
@source '../../../../vendor/tapp/filament-certificate-builder/resources/**/*.blade.php';
Publish and run the migrations:
php artisan vendor:publish --tag="filament-certificate-builder-migrations"
php artisan migrate
If certificate_templates already exists in your app, skip the package create-table migration. Add a token_set column yourself if it is missing.
Optionally publish the config:
php artisan vendor:publish --tag="filament-certificate-builder-config"
Configuration
Published config lives in config/certificate-builder.php. The package ships a single default token set. Override that file in the host app with your own token sets, copy, signers, and assets.
use App\Certificates\CourseCertificateTokenResolver; use Tapp\FilamentCertificateBuilder\Filament\Resources\CertificateTemplates\CertificateTemplateResource; return [ 'default_token_set' => 'course', 'token_sets' => [ 'course' => [ 'label' => 'Course', 'resolver' => CourseCertificateTokenResolver::class, 'tokens' => [ 'recipient_name' => [ 'label' => 'Recipient name', 'sample' => 'Jane Doe', ], 'course_name' => [ 'label' => 'Course name', 'sample' => 'Sample Course', ], 'date_range' => [ 'label' => 'Date range', 'sample' => 'January 15th - March 15th 2026', ], ], 'default_copy' => [ 'certifying_line' => 'This certifies that', 'completed_line' => 'has successfully completed', 'description' => '', ], ], ], 'default_signers' => [ 1 => ['name' => 'Signer 1', 'title' => 'Title'], 2 => ['name' => 'Signer 2', 'title' => 'Title'], 3 => ['name' => 'Signer 3', 'title' => 'Title'], ], 'fallback_assets' => [ 'logo_1' => 'images/certificate-logo.png', 'signature_1' => 'images/signer-one.png', 'signature_2' => 'images/signer-two.png', ], 'media' => [ 'use_signed_urls' => true, 'signed_url_expiration' => 60, ], 'chrome_path' => env('BROWSERSHOT_CHROME_PATH'), 'navigation_group' => 'Certificates', 'navigation_sort' => 55, 'preview_template_route' => null, 'resources' => [ CertificateTemplateResource::class, ], ];
| Key | Purpose |
|---|---|
default_token_set |
Token set used for new templates and CertificateTemplate::defaultTemplate(). |
token_sets |
Named issuing contexts (course, training, etc.). Each has a label, resolver, live tokens, and default static copy. |
default_signers |
Name and title shown on new/reset layouts for signature slots 1–3. |
fallback_assets |
Public asset paths used when a template has no uploaded media for that slot. Keys match media collections (logo_1, signature_1, …). |
media.use_signed_urls |
When true (default), assetUrl() uses temporary/signed URLs on cloud disks (S3, R2, etc.) so private media loads in HTML and PDF. Local disks still use getUrl(). |
media.signed_url_expiration |
Temporary URL lifetime in minutes (default 60). |
chrome_path |
Chrome/Chromium binary for Browsershot PDF generation. Falls back to services.browsershot.chrome_path. |
navigation_group / navigation_sort |
Filament nav placement for the package resource. |
preview_template_route |
Named route for “Preview with sample data” on the edit page. The route receives the template model. Set to null to hide the button. |
resources |
Filament resources registered by the plugin. Set to [] if the host app provides its own CertificateTemplate resource. |
The token-set select on the template form is shown only when token_sets has more than one entry.
Token sets
Each issuing model gets its own token set. The set lists:
label— shown in the token-set selectresolver— class that implementsTapp\FilamentCertificateBuilder\Contracts\ResolvesCertificateTokenstokens— live field keys bound in the designer.samplevalues appear in the designer and preview;labelis the designer field namedefault_copy— static text seeded into new/reset layouts (certifying_line,completed_line,description, plus any extra keys you add)
Existing saved layouts are not rewritten when you change default_copy. That copy is used for new templates and Reset to default.
The package never imports your domain models. Resolvers live in the host app and receive a context array you pass at render time.
Resolver class
Implement ResolvesCertificateTokens and return a string value for every key in that token set:
<?php declare(strict_types=1); namespace App\Certificates; use App\Models\Course; use App\Models\User; use Tapp\FilamentCertificateBuilder\Contracts\ResolvesCertificateTokens; class CourseCertificateTokenResolver implements ResolvesCertificateTokens { /** * @param array<string, mixed> $context * @return array<string, string> */ public function resolve(array $context): array { $course = $context['course'] ?? null; $user = $context['user'] ?? null; if (! $course instanceof Course || ! $user instanceof User) { return [ 'recipient_name' => '', 'course_name' => '', 'date_range' => '', ]; } $recipientName = trim(($user->first_name ?? '').' '.($user->last_name ?? '')); if ($recipientName === '') { $recipientName = (string) ($user->name ?? ''); } return [ 'recipient_name' => $recipientName, 'course_name' => (string) $course->name, 'date_range' => (string) $course->completed_at?->toFormattedDateString(), ]; } }
Register that class on the matching token set:
'token_sets' => [ 'course' => [ 'label' => 'Course', 'resolver' => App\Certificates\CourseCertificateTokenResolver::class, // ... ], ],
Resolve tokens at issue time, then render HTML or PDF:
use Tapp\FilamentCertificateBuilder\Actions\GenerateCertificatePdfAction; use Tapp\FilamentCertificateBuilder\Contracts\ResolvesCertificateTokens; use Tapp\FilamentCertificateBuilder\Support\CertificateLayout; $resolver = app(CertificateLayout::resolverClass($template->tokenSet())); if (! $resolver instanceof ResolvesCertificateTokens) { $tokens = CertificateLayout::sampleTokens($template->tokenSet()); } else { $tokens = $resolver->resolve([ 'course' => $course, 'user' => $user, ]); } $html = view('filament-certificate-builder::certificate', [ 'template' => $template, 'tokens' => $tokens, ])->render(); $pdf = app(GenerateCertificatePdfAction::class)->handle($template, $tokens);
Filament LMS course certificates
tapp/filament-lms can optionally assign a template per course. LMS does not add a package-default course token set — hosts register that set themselves. LMS creates templates from Edit Course (name + token set + default layout) and keeps filament-lms::certificates.show / filament-lms::certificates.download for issue and download. The builder create page does not need a ?course= hook.
When rendering a course certificate, LMS calls your resolver with:
$resolver->resolve([ 'course' => $course, // Tapp\FilamentLms\Models\Course 'user' => $user, ]);
Add a matching course token set in the host config/certificate-builder.php. Hosts that already have certificate_templates skip the package create-table migration (see Installation).
CertificateLayout::sampleTokens($tokenSet) returns the configured sample values. Use that for designer previews when you do not have a live course/user.
The built-in ResolveSampleCertificateTokensAction does exactly that. Point a token set at it if you only need designer samples and have not written a live resolver yet.
Using your own Filament resource
To keep templates in a host resource (custom nav, extra columns, create-from-elsewhere actions), disable the package resource:
'resources' => [],
Your resource can still use the package designer. On the edit page, embed:
use Filament\Schemas\Components\Livewire; use Tapp\FilamentCertificateBuilder\Livewire\CertificateLayoutDesigner; Livewire::make( CertificateLayoutDesigner::class, fn (): array => ['template' => $this->getRecord()], )->key('certificate-layout-designer-'.$this->getRecord()->getKey());
You may extend Tapp\FilamentCertificateBuilder\Models\CertificateTemplate in the host app and point your resource at that model.
Preview route
Set preview_template_route to a named route that accepts the template and returns the package certificate view with sample tokens:
use Tapp\FilamentCertificateBuilder\Models\CertificateTemplate; use Tapp\FilamentCertificateBuilder\Support\CertificateLayout; Route::get('/certificates/templates/{certificateTemplate}/preview', function (CertificateTemplate $certificateTemplate) { return view('filament-certificate-builder::certificate', [ 'template' => $certificateTemplate, 'tokens' => CertificateLayout::sampleTokens($certificateTemplate->tokenSet()), ]); })->name('certificates.preview-template');
Then set 'preview_template_route' => 'certificates.preview-template'.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.