lab404/nova-signature
Nova Signature adds a drawable signature pad field to Laravel Nova resources. Users sign with a mouse, finger, or stylus and the result is saved as an image on your model, with configurable pad options for a smooth signing experience. Simple to install and customize.
Install the field with Composer alongside Nova 2/3 (the package has not been updated since January 2020):
composer require lab404/nova-signature
The field is auto-discovered via its service provider; there is nothing to publish for a basic setup.
First, add a column to the model that will store the signature. The field persists a base64 data-URI string (typically data:image/png;base64,...), so use a text column with headroom:
Schema::table('contracts', function (Blueprint $table) {
$table->longText('signature')->nullable();
});
Then attach the field in the Nova resource. The most important first step is hiding it from the index view, because the raw base64 string will destroy your table layout otherwise:
use Lab404\NovaSignature\NovaSignature;
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title'),
NovaSignature::make('Signature')
->hideFromIndex()
->rules('required', 'max:200000'),
];
}
Cast it to string (or leave it uncast) on the model. A realistic first use case: a contract or waiver resource where a user draws their signature on create/update, and an admin views the rendered image on the detail page.
Basic form capture. Drop the field on the resource, mark it ->hideFromIndex(), and let signature_pad render the canvas. It submits a single string, so no file upload or storage disk is involved.
Detail-view rendering. The stored value is a data URI, so display it as an image rather than text. You have two clean options:
// Option A: inline img through a display callback
NovaSignature::make('Signature')
->hideFromIndex()
->displayUsing(function ($value) {
return $value ? "<img src=\"{$value}\" style=\"max-width:300px\">" : '—';
})->asHtml(),
// Option B: write the decoded file to storage on save and show the URL
Option B is the pattern most production teams end up with: decode the data URI in an observer or saving model event, store the PNG on a disk, and keep the base64 only long enough to convert it. This keeps rows small and lets you generate PDFs or signed documents later.
Conversion helper for downstream use. When you need the signature outside Nova (PDF generation, e-mail attachments, API responses), normalize it:
protected function signaturePng(?string $value): ?string
{
if (! $value || ! str_starts_with($value, 'data:image/png;base64,')) {
return null;
}
return base64_decode(substr($value, strlen('data:image/png;base64,')));
}
Multiple signatures on one resource. Create separate columns (customer_signature, witness_signature) and separate field instances. Avoid array/JSON storage here — the field submits a flat string and you would be fighting the package.
Validation pattern. There is no built-in server-side validation, so pair a length rule with a small custom rule or closure that checks the prefix and that base64_decode succeeds. This prevents garbage from non-browser clients hitting your table.
Conditional fields. Combine with Nova's ->dependsOn() / ->onlyOnForms() as usual. Signing on create is common; editing a signature later should usually require an explicit "re-sign" toggle, so consider ->readonly() once a boolean signed_at is set.
Form submission guard. If you add a "clear" workflow, make sure the field can be explicitly emptied (nullable column plus a sometimes rule), otherwise users get stuck re-signing to overwrite.
TEXT is capped at 65,535 bytes, and a large canvas at high pixel density can approach or exceed that once base64-encoded (~33% overhead). Use LONGTEXT or MEDIUMTEXT and cap the canvas size client-side.hideFromIndex() should be considered mandatory, and hideFromDetail() too if you use a decoded file URL instead.nova-components/ or copy the field class and Vue component into your app so you can patch it. For many teams, wrapping signature_pad in a homegrown Nova field is less risk than depending on this repo.null, an empty string, or a bare data:, prefix. Guard every render and decode with a strict str_starts_with('data:image/png;base64,') check.tool.scss/resource styles if needed.touch-action: none on the canvas for stylus/touch devices; if the page scrolls while drawing on a tablet, that CSS is missing or being overridden by Nova's styles.longText column. Enforce a max length rule, validate the data-URI prefix server-side, and consider re-encoding the PNG server-side before persisting.value/handleChange wiring isn't being clobbered by your field's ->default() or ->fillUsing().displayUsing(), fillUsing(), model observers, and direct Vue component edits rather than through documented hooks; there is no configuration file to rely on.How can I help you explore Laravel packages today?