kukux / digital-signature
A Laravel Filament package for digital signature capture, certificate issuance, and PDF signing (v3 + v4 + v5 compatible).
Requires
- php: ^8.2
- ext-gd: *
- ext-openssl: *
- filament/filament: ^3.0 || ^4.0 || ^5.0
- laravel/framework: ^12.0
- setasign/fpdi: ^2.5
- tecnickcom/tcpdf: ^6.7
Requires (Dev)
- fpdf/fpdf: ^1.86
- laravel/pint: ^1.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^2.0 || ^3.0
- pestphp/pest-plugin-laravel: ^2.0 || ^3.0
- phpstan/phpstan: ^1.10
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.8.2
- v1.8.1
- v1.8.0
- v1.7.10
- v1.7.9
- v1.7.8
- v1.7.7
- v1.7.6
- v1.7.5
- v1.7.4
- v1.7.3
- v1.7.2
- v1.7.1
- v1.7.0
- v1.6.1
- v1.6.0
- v1.5.6
- v1.5.5
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.8
- v1.4.7
- v1.4.6
- v1.4.5
- v1.4.4
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.10
- v1.3.9
- v1.3.8
- v1.3.7
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.0.26
- v1.0.25
- v1.0.24
- v1.0.23
- v1.0.22
- v1.0.21
- v1.0.20
- v1.0.19
- v1.0.18
- v1.0.17
- v1.0.16
- v1.0.15
- v1.0.14
- v1.0.13
- v1.0.12
- v1.0.11
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.0
This package is auto-updated.
Last update: 2026-10-01 06:52:52 UTC
README
A Laravel Filament plugin for capturing signatures, issuing X.509 certificates, and embedding cryptographically signed stamps into PDF documents.
Supports: Filament v3, v4 and v5 — Laravel 12 — PHP 8.2+
Documentation
📖 Read the documentation — the same pages as below, with navigation and search.
| Doc | Description |
|---|---|
| Implementation Plan | Start here. Phase-by-phase plan for integrating the package into your app |
| Installation | Composer, migrations, plugin registration, admin resource |
| Configuration | All config keys and env variables |
| Model Setup | Signable interface and HasSignatures trait |
| Filament Components | SignaturePad, SignatureColumn, SignatureResource, SignDocumentAction |
| Signing Workflow | Full lifecycle and SignatureManager API |
| Ad-hoc Signing | Implement document signing outside a package resource |
| Certificates | Certificate issuance, CA setup, CFSSL |
| Signatory Routing | Role-bound slots, signing sessions, consent models, multi-signatory documents, Filament version compatibility |
| Security | HMAC metadata, machine binding, DB cross-validation, forgery detection |
Requirements
- PHP 8.2+ with
ext-opensslandext-gd - Laravel 12
- Filament 3, 4, or 5
Quick Install
composer require kukux/digital-signature php artisan vendor:publish --tag=signature-migrations php artisan vendor:publish --tag=signature-config php artisan migrate php artisan filament:assets
filament:assets publishes the plugin's JS bundle (signature pad + picker) so it's reachable from the panel — re-run it after every composer update of this package.
Register the plugin in your panel provider:
// app/Providers/Filament/AdminPanelProvider.php use Kukux\DigitalSignature\SignaturePlugin; ->plugins([ SignaturePlugin::make() ->navigationGroup('Documents') // optional ->navigationIcon('heroicon-o-pencil-square') // optional ->navigationSort(10), // optional ])
This registers:
- A floating launcher — a button on every panel page whose slide-over shows what's awaiting the signed-in user, with Sign / Decline on each row
- Signatures — a full admin resource for registering reusable signature images and viewing signature records
- Sign Document — header actions inside the Signatures resource for signing with a registered signature
The floating launcher
Signing is an interruption, not a destination. Rather than adding sidebar items a signatory has to go looking for, the plugin pins a button to every page of the panel; clicking it slides over the documents waiting on them.
SignaturePlugin::make() // launcher on (default) SignaturePlugin::make()->withoutFloatingLauncher() // off; sidebar items return
While the launcher is on, the inbox page and the Signatures resource stop
claiming navigation items — both stay routable, and the slide-over links to
them. Keep the launcher and the sidebar entries with
SIGNATURE_LAUNCHER_REPLACES_NAV=false.
It won't land on your own floating button. A plugin doesn't own the corner
it's dropped into, so before settling the launcher measures what the host app
already has pinned there — a FAB, a chat widget, a cookie bar — and stacks
itself clear, re-measuring on resize and when widgets mount late. Sidebars and
other full-height layout are floated over rather than stacked above. Where the
detector guesses wrong, name the widget in launcher.avoid / launcher.ignore;
where you already know the answer, set launcher.offset and turn
avoid_overlap off.
Position, icon, label, brand colour, offsets, z-index and badge poll interval are all config; see Configuration.
Preparing a Signable Model
Any model whose PDF can be signed must implement Signable and use HasSignatures.
use Kukux\DigitalSignature\Contracts\Signable; use Kukux\DigitalSignature\Traits\HasSignatures; class Contract extends Model implements Signable { use HasSignatures; public function getSignableTitle(): string { return $this->title; } public function getSignablePdfPath(): string { return $this->pdf_path; } public function getSignableId(): int|string { return $this->id; } }
Adding the Sign Action to Your Own Resource
First let the signer register a reusable signature from the built-in Signatures resource. Then add SignDocumentAction to any resource whose model implements Signable.
use Kukux\DigitalSignature\Filament\Actions\SignDocumentAction; use Kukux\DigitalSignature\Filament\Columns\SignatureColumn; class ContractResource extends Resource { public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('title'), SignatureColumn::make('signature')->thumbSize(80, 32), ]) ->actions([ SignDocumentAction::make() ->stampAt(page: 1, x: 100, y: 650, w: 200, h: 80), ]); } }
For controller-driven or custom page flows, see Ad-hoc Signing.
Built-in Signatures Admin Resource
When the plugin is registered, a Signatures resource appears in the sidebar automatically.
Register SignaturePlugin::make() on every Filament panel that should use the package. If a panel discovers or registers SignatureResource without the plugin, Filament can report Plugin [signature] is not registered for panel [admin].
List page: a table of all signature records with thumbnail, signer, status and method. Each row has View (a slide-over with the details), Download and Revoke.
There is no separate View page. Manage signatures in the launcher drawer widens the drawer to show every signature's details, download, revoke, usage history and the PDF templates it can be applied to, without leaving the current page.
Customize appearance:
SignaturePlugin::make() ->navigationGroup('Documents') ->navigationIcon('heroicon-o-pencil-square') ->navigationSort(10) ->navigationLabel('Document Signatures') // Disable the resource entirely (bring your own): SignaturePlugin::make()->withoutResource()
Multi-Signatory Documents
When a document is signed by roles rather than by whoever opens it — an Accomplishment Report with Prepared by, Attested by and Noted by — declare the roles on the template and let the plugin find the people:
// config/signature.php 'templates' => [ 'accomplishment-report' => [ 'view' => 'pdf.accomplishment-report', 'signable' => \App\Models\AccomplishmentReport::class, 'slots' => [ 'prepared_by' => ['label' => 'Prepared by', 'signatory' => 'preparedBy', 'order' => 1, 'required' => true], 'attested_by' => ['label' => 'Attested by', 'signatory' => 'attestedBy', 'order' => 2, 'required' => true], 'noted_by' => ['label' => 'Noted by', 'signatory' => 'notedBy', 'order' => 3, 'required' => true], ], ], ],
class AccomplishmentReport extends Model implements Signable { use HasPdfTemplate, HasSignatories; protected string $signaturePdfTemplate = 'accomplishment-report'; public function preparedBy(): BelongsTo { return $this->belongsTo(User::class, 'prepared_by_id'); } public function attestedBy(): BelongsTo { return $this->belongsTo(User::class, 'attested_by_id'); } public function notedBy(): BelongsTo { return $this->belongsTo(User::class, 'noted_by_id'); } }
// In your resource SignatoryPanel::make('signatories'); // who signs, and where they're up to RequestSignaturesAction::make(); // freeze the PDF and ask them
Each person registers their signature once in their own panel. Being tagged on a record is then enough for the document to reach them — the plugin resolves the person, finds their signature, pre-fills the placement, and lists the document in their Awaiting my signature inbox.
On consent. By default the plugin never signs for anyone: the signature is always produced in the signatory's own authenticated request, with their own certificate. Truly hands-off signing requires that person to grant a scoped, expiring, revocable authorisation from their own account — and every use of it is audited and notified. See Signatory Routing.
Security Highlights
| Feature | Default |
|---|---|
| PKCS#7 cryptographic signature embedded in PDF | Always on |
| DocMDP P=2 — post-signing modification detection | Always on |
| HMAC-signed PNG metadata (tEXt + XMP) | Always on |
| XMP metadata visible in macOS Preview & Windows Explorer | Always on |
| Signer identity (name + email) embedded in PNG | Always on |
| Forgery / screenshot upload rejection | Always on |
| Document integrity hashes (before + after) | Always on |
| Machine binding — DB cross-validation on re-upload | Always on |
| Signature chain hashes across multi-signatory documents | Always on |
| Audit row for every auto-affixed signature | Always on |
| Auto-signing on someone's behalf | Off — requires their explicit grant |
| Machine lock — reject re-upload from different device | SIGNATURE_MACHINE_LOCK=true |
| CRL certificate revocation check | SIGNATURE_CRL_ENABLED=true |
| RFC 3161 trusted timestamp via TSA | SIGNATURE_TSA_URL=https://... |
For full details see docs/security.md.
Queue
Signing runs asynchronously. Start a queue worker:
php artisan queue:work
To sign synchronously (no queue required):
SignDocumentAction::make() // default is now synchronous
The action calls embedAndFinalize() directly unless you opt in to queued signing:
SignDocumentAction::make()->queued()