#Installation
#Requirements
- PHP 8.2+ with
ext-opensslandext-gd - Laravel 12 or 13 (Laravel 13 needs PHP 8.3+)
- Filament 3, 4, or 5
Laravel 11 is not supported. Every 11.x release, up to and including the final 11.56.1, is affected by PKSA-mdq4-51ck-6kdq (CRLF injection in the default email rule, patched in 12.60.0). Laravel 11 is past its security-support window, so no fixed 11.x will ever be released. Composer 2.9 blocks advisory-affected packages by default, which makes the whole line uninstallable — this package dropped the constraint rather than ask you to disable that protection.
#Quick install
composer require kukux/digital-signature
php artisan signature:install
At the end of composer require you'll see a reminder to run the installer. It lists what it's about to do, asks once, then logs each step:
Checking requirements ................................. DONE
Publishing config ..................................... DONE
Updating .env ......................................... DONE
Publishing assets ..................................... DONE
Checking storage ...................................... DONE
Running migration ..................................... DONE
Registering plugin .................................... DONE
Registering policy .................................... DONE
INFO Digital Signature is installed.| Step | What it does |
|---|---|
| Requirements | Checks PHP, ext-openssl, ext-gd and Filament. Warns if DomPDF or Imagick is missing. |
| Config | Publishes config/signature.php. If you already have one, it leaves it alone and lists any keys it's missing (why that matters). |
.env | Adds the starter settings to .env and .env.example. It only adds missing keys and never changes a value. |
| Assets | Runs filament:assets. |
| Storage | Stops the install if SIGNATURE_DISK is a public disk. |
| Migration | Lists what will run, then runs it. |
| Plugin | Adds SignaturePlugin::make() to the panel providers you pick. |
| Policy | Writes app/Policies/DigitalSignaturePolicy.php and registers it in AppServiceProvider (details). |
Before editing a panel provider or AppServiceProvider, it shows the lines it will add and asks. If the edited file doesn't pass php -l, it puts the original back. If a file isn't laid out the way it expects, it prints the snippet for you to paste and marks the step MANUAL.
Run it again any time. It only does what's missing and marks the rest SKIPPED.
| Flag | Effect |
|---|---|
--dry-run | Show what each step would do, change nothing. A good first run on an existing app. |
-n | No questions, for CI. Code edits become MANUAL unless you add --force. |
--force | Replace the published config, migrate in production, and edit code without asking. |
--no-migrate | Skip the migration, e.g. when deploys run it. |
--no-assets / --no-panel / --no-policy | Skip that step. |
--panel=admin | Register the plugin on this panel id only. |
--publish-migrations | Publish the migration to database/migrations and run that copy. |
--agent | Turn on the desktop agent and pin its server ID and salt, so rotating APP_KEY later doesn't break pairings. |
In production the installer won't migrate without --force. It never changes an existing .env value or touches APP_KEY.
The rest of this page is what the installer does, step by step, if you'd rather do it by hand.
#1. Install via Composer
composer require kukux/digital-signature
#2. Publish config and migrate
php artisan vendor:publish --tag=signature-config
php artisan migrate
The package's migration runs straight from vendor/, so there is nothing to
publish. It only creates the tables and columns that are missing, so the same
php artisan migrate works for a fresh install and for an upgrade from any
earlier release.
The migration is named 9999_12_31_000000_create_digital_signature_tables.php
so it always runs after your app's own migrations, including your users
table. The flip side is that if one of your migrations adds a foreign key to a
package table, publish the migration (below) and give it a timestamp earlier
than yours.
If you need to edit the schema, publish the migration instead:
php artisan vendor:publish --tag=signature-migrations
Once a published copy exists in database/migrations, the package stops
loading its own, so the migration never runs twice. Publishing again does not
create a second copy.
Upgrading from v1.8.x or earlier with published migrations? Those fifteen
*_create_digital_*files indatabase/migrationshave already run. Leave them in place. The new consolidated migration sees the existing tables and only adds what is missing. Itsdown()will not drop tables that the old migrations created.
#Keep your published config current
Laravel merges config/signature.php with the package defaults one level
deep. A top-level block you deleted falls back to the package's version, but
a block you kept replaces the package's version entirely.
So if your copy has a trimmed 'launcher' => ['position' => 'bottom-left'],
every other launcher key, and every SIGNATURE_LAUNCHER_* variable, is
ignored. Either keep a block whole, or delete it and set it from .env. After
upgrading the package, diff your copy against
vendor/kukux/digital-signature/config/signature.php for new blocks.
#Starter .env
The defaults work, but these are the settings worth deciding up front:
# Certificates and signed PDFs. Must be a PRIVATE disk; `local` is
# storage/app/private. Never `public`.
SIGNATURE_DISK=local
SIGNATURE_CERT_DRIVER=openssl
SIGNATURE_PDF_DRIVER=fpdi
# Consent. `approval` never signs on someone's behalf: routing finds the
# signatory and places the block, but they sign in their own session.
SIGNATURE_AUTO_AFFIX_MODE=approval
SIGNATURE_ALLOW_IMPLICIT_AFFIX=false
SIGNATURE_AUTO_AFFIX_NOTIFY=true
# Signing order and how long an unfinished session stays open.
SIGNATURE_SEQUENCE_MODE=sequential # or parallel
SIGNATURE_MULTI_MODE=progressive # or incremental (one certificate per signer)
SIGNATURE_SESSION_EXPIRY_DAYS=180
# Blocks re-uploading a signature from another machine. Leave it on.
SIGNATURE_MACHINE_LOCK=true
# Only turn on if your CA publishes a CRL.
SIGNATURE_CRL_ENABLED=false
# RFC 3161 timestamping. Blank = off. Free option: https://freetsa.org/tsr
SIGNATURE_TSA_URL=
# Desktop agent pairing. Its server ID and salt derive from APP_KEY unless
# you set SIGNATURE_AGENT_SERVER_ID / SIGNATURE_AGENT_SALT. Set them before
# you ever rotate APP_KEY, or every paired computer has to pair again.
SIGNATURE_AGENT_ENABLED=true
# Pairing from a virtual machine is refused by default. Leave this out in
# production; set it empty on a dev server to test from Parallels or UTM.
# SIGNATURE_AGENT_BLOCKED_DEVICE_TYPES=
# Resolution of the page previews in the placement designer.
SIGNATURE_DESIGNER_DPI=144
The full list is in Configuration.
Then publish the plugin's JS bundle so the signature pad and picker components work in the browser:
php artisan filament:assets
filament:assets reads the asset registered via FilamentAsset::register() in SignaturePlugin and links it under public/js/filament/kukux/digital-signature/. Run it again after every composer update of this package so the published JS stays in sync with the installed version.
#3. Register the plugin
Add SignaturePlugin to your Filament panel provider.
// app/Providers/Filament/AdminPanelProvider.php
use Kukux\DigitalSignature\SignaturePlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
SignaturePlugin::make(),
]);
}This automatically registers:
- Signatures resource — register reusable signature images and view signature records in the admin panel
- Sign Document actions — actions inside the Signatures resource for signing with a registered signature
Do not rely on discoverResources() to pick up the package resource from vendor. The supported setup is to register SignaturePlugin::make() on each Filament panel that should show the signature resource.
Got more than one panel? Register the plugin on every panel where people sign or use the designer, not just the one with the menu item. The signer page, designer and inbox look up the plugin from the panel they render on.
#Authorization
Kukux\DigitalSignature\Models\Signature lives in vendor/, so Laravel's
policy auto-discovery never finds a policy for it. Register one yourself. A
signature should only ever be usable by its owner, admins included:
// app/Providers/AppServiceProvider.php
Gate::policy(
\Kukux\DigitalSignature\Models\Signature::class,
\App\Policies\DigitalSignaturePolicy::class,
);// app/Policies/DigitalSignaturePolicy.php
class DigitalSignaturePolicy
{
public function viewAny(User $user): bool { return true; }
public function create(User $user): bool { return true; }
public function view(User $user, Signature $signature): bool { return $this->owns($user, $signature); }
public function update(User $user, Signature $signature): bool { return $this->owns($user, $signature); }
public function delete(User $user, Signature $signature): bool { return $this->owns($user, $signature); }
// Deleting signatures in bulk would wipe the history of every document they signed.
public function deleteAny(User $user): bool { return false; }
public function forceDelete(User $user, Signature $signature): bool { return false; }
private function owns(User $user, Signature $signature): bool
{
return (int) $signature->user_id === (int) $user->getAuthIdentifier();
}
}Using Filament Shield? Don't call it
SignaturePolicy.shield:generate --allwrites a policy under that name for this model and overwrites yours. A different name keeps it safe.Shield's
Gate::beforealso gives super admins every ability, so they skip this policy. The resource still only lists each person's own signatures, so the record check matters mostly for a guessed URL.
#4. Configure the admin resource (optional)
Customize the Signatures resource navigation appearance using fluent methods on the plugin:
SignaturePlugin::make()
->navigationIcon('heroicon-o-pencil-square') // default icon
->navigationGroup('Documents') // group in sidebar (null = ungrouped)
->navigationSort(10) // sort position
->navigationLabel('Document Signatures') // custom sidebar labelOr via .env:
SIGNATURE_RESOURCE_ICON=heroicon-o-pencil-square
SIGNATURE_RESOURCE_GROUP=Documents
SIGNATURE_RESOURCE_SORT=10
SIGNATURE_RESOURCE_LABEL=Signatures
SIGNATURE_RESOURCE_ENABLED=true
To hide the resource entirely (e.g. when building your own):
SignaturePlugin::make()->withoutResource()#5. Register PDF templates (optional)
If your app produces fixed-layout PDFs (DTRs, payslips, contracts) that should be sign-able through the plugin's placement designer, register them via templates():
SignaturePlugin::make()
->templates([
\App\Pdf\DtrTemplate::class,
\App\Pdf\PayslipTemplate::class,
])If you run php artisan config:cache (you should in production), register
templates as class-strings. A closure in config/signature.php, like a
data_resolver or a slot's signatory, makes config:cache fail. Put those
in a template class and list the class instead:
// config/signature.php
'templates' => [
\App\Pdf\AccomplishmentReportTemplate::class,
],See PDF Templates for the PdfTemplate contract and slot definitions. This is purely additive — Signable models without a template continue to work via the ad-hoc and on-demand flows.
If you see Plugin [signature] is not registered for panel [admin], check that the plugin is registered on the same panel that is rendering the resource:
// app/Providers/Filament/AdminPanelProvider.php
use Kukux\DigitalSignature\SignaturePlugin;
$panel->plugins([
SignaturePlugin::make(),
]);In multi-panel apps, register the plugin on every panel that uses the package resource or actions.
#6. Start the queue worker
By default, SignDocumentAction signs synchronously — no queue needed. If you opt into queued signing (.queued()), start a worker:
php artisan queue:work
#7. Publish views and assets (optional)
Customise the Blade templates by publishing them into your app's view directory:
php artisan vendor:publish --tag=signature-views
To copy the compiled JS into public/vendor/digital-signature/ (instead of letting Filament symlink it via filament:assets), use:
php artisan vendor:publish --tag=signature-assets
This is only needed when you're serving the JS directly outside Filament's asset pipeline — most projects should use php artisan filament:assets from step 2 instead. If you publish manually, re-run this command after every composer update of this package.