#Filament Components
#SignaturePlugin — panel plugin
Registers the Signatures resource on your Filament panel.
#Basic registration
// app/Providers/Filament/AdminPanelProvider.php
use Kukux\DigitalSignature\SignaturePlugin;
->plugins([
SignaturePlugin::make(),
])Register the plugin on each panel that should use the package. Avoid manually discovering the package resource from vendor; the plugin registers it for you.
#Fluent configuration
SignaturePlugin::make()
->navigationIcon('heroicon-o-pencil-square') // sidebar icon (default: heroicon-o-pencil-square)
->navigationGroup('Documents') // sidebar group (default: none)
->navigationSort(10) // sort position (default: none)
->navigationLabel('Document Signatures') // sidebar label (default: "Signatures")#Disabling parts
// Hide the Signatures resource (bring your own resource):
SignaturePlugin::make()->withoutResource()
// Disable the resource via env (useful for non-admin panels):
// SIGNATURE_RESOURCE_ENABLED=false
// Keep the floating launcher off this panel (restores the sidebar items):
SignaturePlugin::make()->withoutFloatingLauncher()#Floating launcher — the default entry point
Registering the plugin mounts a floating button on every page of the panel, via
a PanelsRenderHook::BODY_END render hook. Clicking it opens a slide-over with
the documents waiting on the signed-in user, each with Sign and Decline, plus
links to the full inbox and their signature library.
It exists because signing is an interruption, not a destination. A signatory is somewhere else in the app when a document reaches them, and making them leave that page, find a sidebar item under whatever navigation group the host app chose, act, and navigate back is most of the friction in a signing flow.
// On by default — nothing to register.
SignaturePlugin::make()
// Off for this panel; the inbox page and Signatures resource return to the sidebar.
SignaturePlugin::make()->withoutFloatingLauncher()
// Conditionally, e.g. staff panel only.
SignaturePlugin::make()->withFloatingLauncher($panel->getId() === 'staff')Appearance and behaviour are config — position, icon, label, brand colour, badge poll interval — see Configuration.
It takes navigation with it. While the launcher is on, the inbox page and
the Signatures resource stop registering navigation items; both stay routable
and the slide-over links to them. Set
signature.launcher.replaces_navigation to false to keep both.
The slide-over shares ActsOnSignatureRequests with the full-page inbox, so
the two surfaces cannot disagree about what a signatory may do: same query,
same ownership checks, same exception handling, and the signature is still
produced inside that user's own authenticated request with their own
certificate.
#It gets out of the host app's way
A plugin does not own the corner it is dropped into. Before settling, the
button measures what the host app already has pinned there — its own FAB, a
chat widget, a cookie bar — and stacks itself clear of it, re-measuring on
resize, on livewire:navigated, and when a widget mounts late.
Tall fixed elements (sidebars, drawers, backdrops) are treated as layout and
floated over rather than stacked above; pointer-events: none decoration such
as a toast rail is ignored; and a hopelessly crowded corner is left alone
rather than drifting the button into mid-page. Two config lists,
launcher.avoid and launcher.ignore, override the detector per selector, and
launcher.offset + avoid_overlap => false place the button by hand when you
already know where it belongs. See
Configuration.
The algorithm is covered by npm run test:js, which runs the shipped code —
extracted from this view at run time — against a simulated DOM.
Two things it does not need: a build step (the styles are namespaced and inline, so no host Tailwind utility can go missing under it) and its own JS bundle (Alpine handles open/close and placement, Livewire handles data).
Mounting it yourself — outside a panel, or in a custom layout:
<livewire:kukux-digital-signature.launcher />#SignatureResource — admin resource
Registered automatically by SignaturePlugin. Provides a full admin interface for managing signature records.
#List page
- Table with signature thumbnail, signer name + email, status badge, capture method, and dates
- Per-row View and Revoke actions
- Header Add Signature action for registering a reusable signature
- Header Sign Document action for signing with a registered signature
- Filters for status and capture method
#View page
- Large signature image with dark-mode support
- Signer name, email, status, capture method, signed-at timestamp
- Collapsible Security Metadata section: UUID, image hash, device fingerprint, certificate fingerprint (all copyable)
- Header actions: Sign Document, Download Image, Revoke
#Using with your own resource
If you only want the resource's components without the built-in pages, disable it and build your own:
SignaturePlugin::make()->withoutResource()Then add SignDocumentAction and SignatureColumn to your own resource as described below.
#SignaturePad — form field
Renders a signature capture widget inside any Filament form. Supports a draw tab (canvas with brush controls) and an upload tab (file input). Fully dark-mode compatible.
#Basic usage
use Kukux\DigitalSignature\Filament\Fields\SignaturePad;
SignaturePad::make('signature_data')
->label('Your Signature')#All options
SignaturePad::make('signature_data')
->canvasWidth(600) // drawing area width (default: 600 px)
->canvasHeight(200) // drawing area height (default: 200 px)
->penColor('#1a1a1a') // initial stroke colour (default: #1a1a1a)
->penWidth(0.5, 2.5) // min / max stroke width (default: 0.5, 2.5)
->confirmLabel('Accept') // confirm button label (default: "Confirm")
->withoutUploadTab() // hide the upload tab
->withoutDrawTab() // hide the draw tab (upload-only mode)
->withoutClearBtn() // hide the clear button
->withoutUndoBtn() // hide the undo button#Draw-only mode (recommended for strict authenticity)
SignaturePad::make('signature_data')->withoutUploadTab()#State
The field stores a base64 PNG data URI (data:image/png;base64,...) or null when empty.
#SignatureColumn — table column
Displays a signature thumbnail and status badge in a Filament table. Adapts to dark mode via CSS invert.
#Basic usage
use Kukux\DigitalSignature\Filament\Columns\SignatureColumn;
SignatureColumn::make('signature')#Custom thumbnail size
SignatureColumn::make('signature')
->thumbSize(120, 48) // width, height in pixels (default: 80 × 32)#How it resolves the image
The column calls latestSignature() on the row model if that method exists, or falls back to $record->signature. It reads image_path and resolves the URL through the configured storage disk.
For disks that support temporary URLs (S3, etc.) the URL expires after 5 minutes.
Note:
SignatureColumnis designed for use in resources where the row model implementsSignable(e.g.ContractResource). For the built-inSignatureResourcewhere the row IS the signature, the resource usesImageColumndirectly.
#SignDocumentAction — action
A Filament action that opens a modal where the current user selects one of their registered signatures, enters a certificate password when needed, and signs the document when submitted.
Before using this action, the signer must have a stored signature record. Users can create one from the built-in Signatures resource, or you can create one yourself with SignatureManager::store().
#In a resource header
use Kukux\DigitalSignature\Filament\Actions\SignDocumentAction;
protected function getHeaderActions(): array
{
return [
SignDocumentAction::make(),
];
}Use header actions only when the page can provide a Signable record to the action. For document-specific signing, table row actions are usually the clearest integration.
#In a table row
->actions([
SignDocumentAction::make()
->stampAt(page: 1, x: 100, y: 650, w: 200, h: 80),
])#Placing the signature stamp on the PDF
stampAt() controls where the signature image is drawn on the signed PDF. Coordinates are in PDF units from the bottom-left origin.
SignDocumentAction::make()
->stampAt(
page: 1,
x: 100.0,
y: 650.0,
w: 200.0,
h: 80.0,
)#Synchronous vs queued signing
By default the action calls embedAndFinalize() directly (synchronous — no queue worker needed):
SignDocumentAction::make() // synchronous (default)
SignDocumentAction::make()->queued() // dispatches EmbedSignatureJob to queue#What happens internally
- Reads
signature_idandpasswordfrom the submitted form - Validates the selected signature belongs to the authenticated user
- Rejects revoked signatures
- Copies the selected signature image into a new document-specific
Signaturerecord linked to the currentSignablerecord - Applies the
stampAt()position when one is configured - Uses the stored certificate password, or the submitted password when no stored password exists
Calls
embedAndFinalize()or queuesEmbedSignatureJob:- CRL check (if enabled)
- PKCS#7 PDF signing
- Captures signed-document hash
#Built-in exception handling
SignDocumentAction catches and surfaces these as Filament danger notifications automatically — no extra code needed in your resource:
| Exception | Notification title |
|---|---|
ForgedSignatureException | "Invalid signature image" |
#Ad-hoc signing
For custom resources, controllers, or pages that need to sign documents outside the built-in resource, see Ad-hoc Signing.