Filament v3 Custom Field Plugin: Signature Pad | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Filament v3 Custom Field Plugins: Building a Reusable Signature Pad Component

 Filament v3 Custom Field Plugins: Building a Reusable Signature Pad Component
==============================================================================

 Learn how to build a production-ready Filament v3 custom field plugin — a signature pad — covering Alpine.js state, Livewire entanglement, PHP-side casting, and package distribution.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 5 Sep 2026 · Updated 5 Sep 2026 · 3 min read

ShareCopy linkCopied

 ![Filament v3 Custom Field Plugins: Building a Reusable Signature Pad Component](https://cdn.msaied.com/633/6d6839e7007d1cb38f0421594a4557bf.png) 

  On this page +1. [Why Build a Custom Filament Field Plugin?](#why-build-a-custom-filament-field-plugin)
2. [The Component Skeleton](#the-component-skeleton)
3. [Alpine.js + Livewire Entanglement](#alpinejs-livewire-entanglement)
4. [PHP-Side: Validation and Casting](#php-side-validation-and-casting)
5. [Package Auto-Discovery](#package-auto-discovery)
6. [Takeaways](#takeaways)

 Why Build a Custom Filament Field Plugin?
-----------------------------------------

Filament ships with a rich field library, but real-world SaaS products inevitably need domain-specific inputs — signature pads, rich colour pickers, geo-coordinate selectors. Understanding how to build one correctly means you can extend Filament without fighting its internals.

This article walks through a **signature pad** field: a canvas-based input that stores a base64 PNG, wired cleanly through Livewire and castable to a value object on the Eloquent model.

---

The Component Skeleton
----------------------

Filament custom fields extend `Filament\Forms\Components\Field`. The minimum surface area is:

```php
namespace Acme\SignaturePad;

use Filament\Forms\Components\Field;

class SignaturePad extends Field
{
    protected string $view = 'signature-pad::signature-pad';

    public function getDefaultState(): mixed
    {
        return null;
    }
}

```

Register the view namespace in your service provider:

```php
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'signature-pad');
}

```

---

Alpine.js + Livewire Entanglement
---------------------------------

The tricky part is syncing canvas data back to Livewire's state. Filament uses `$wire.entangle` under the hood for its own fields, and you should too.

```blade
{{-- resources/views/signature-pad.blade.php --}}

            Clear

```

The Alpine component:

```javascript
Alpine.data('signaturePad', (state) => ({
    state,
    pad: null,

    init() {
        const canvas = this.$refs.canvas;
        this.pad = new SignaturePad(canvas); // signature_pad npm package

        if (this.state) {
            this.pad.fromDataURL(this.state);
        }

        this.pad.addEventListener('endStroke', () => {
            this.state = this.pad.toDataURL();
        });
    },

    clear() {
        this.pad.clear();
        this.state = null;
    },
}));

```

The `@entangle($getStatePath())` call binds the Alpine `state` property directly to Filament's Livewire component state path — no custom events needed.

---

PHP-Side: Validation and Casting
--------------------------------

Add a validation rule to reject empty submissions when the field is required:

```php
public function getValidationRules(): array
{
    return array_merge(parent::getValidationRules(), [
        $this->getStatePath() => [
            fn () => function (string $attribute, mixed $value, \Closure $fail) {
                if ($this->isRequired() && blank($value)) {
                    $fail('A signature is required.');
                }
                if ($value && !str_starts_with($value, 'data:image/png;base64,')) {
                    $fail('Invalid signature format.');
                }
            },
        ],
    ]);
}

```

On the model, a custom cast keeps the domain layer clean:

```php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class SignatureCast implements CastsAttributes
{
    public function get($model, string $key, mixed $value, array $attributes): ?Signature
    {
        return $value ? new Signature($value) : null;
    }

    public function set($model, string $key, mixed $value, array $attributes): array
    {
        return [$key => $value instanceof Signature ? $value->dataUrl : $value];
    }
}

```

```php
// On the Eloquent model
protected $casts = [
    'signature' => SignatureCast::class,
];

```

---

Package Auto-Discovery
----------------------

For distribution, declare the service provider in `composer.json`:

```json
"extra": {
    "laravel": {
        "providers": [
            "Acme\\SignaturePad\\SignaturePadServiceProvider"
        ]
    }
}

```

Publish assets via the service provider:

```php
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'signature-pad');

    $this->publishes([
        __DIR__.'/../resources/dist' => public_path('vendor/signature-pad'),
    ], 'signature-pad-assets');
}

```

Users run `php artisan vendor:publish --tag=signature-pad-assets` once, then reference the JS in their Filament panel's `renderHook` or `vite.config.js`.

---

Takeaways
---------

- Extend `Field`, point to a Blade view, and let `@entangle($getStatePath())` handle Livewire sync — no custom events.
- Keep validation inside `getValidationRules()` so Filament's error display works automatically.
- Use a dedicated Eloquent cast to keep raw base64 out of your domain objects.
- Publish JS assets separately from views so consumers can version them independently.
- Declare the service provider in `composer.json` extras for zero-config auto-discovery.

- [filament](https://www.msaied.com/public/articles?search=filament)
- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [livewire](https://www.msaied.com/public/articles?search=livewire)
- [alpine](https://www.msaied.com/public/articles?search=alpine)

 Frequently asked questions 
---------------------------

  How does @entangle work inside a Filament custom field view?`@entangle($getStatePath())` compiles to a Livewire JS entangle call using the field's dot-notation state path. Any Alpine property bound to it stays in sync with the Livewire component's state without manual event dispatching.

   Can I use this pattern for fields that store structured data instead of a string?Yes. If your field stores JSON (e.g. coordinates), return an array from `getDefaultState()`, encode/decode in the cast, and ensure Alpine serialises the value to a JSON string before assigning it to the entangled state property.

   Do I need to register the Alpine component globally or can it be scoped?Filament loads Alpine after its own scripts, so you can register via `document.addEventListener('alpine:init', ...)` in a published JS asset, or inline the `Alpine.data` call in a `@push('scripts')` block in your Blade view.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleTaylor Otwell Disabled GitHub Issues on Most Laravel Open-Source Packages](https://www.msaied.com/public/articles/taylor-otwell-disabled-github-issues-on-most-laravel-open-source-packages) [Next articleFilament v3 Infolist Entries: Building Rich Read-Only Detail Pages Without Blade Sprawl](https://www.msaied.com/public/articles/filament-v3-infolist-entries-building-rich-read-only-detail-pages-without-blade-sprawl)  

   On this page
-------------

1. [Why Build a Custom Filament Field Plugin?](#why-build-a-custom-filament-field-plugin)
2. [The Component Skeleton](#the-component-skeleton)
3. [Alpine.js + Livewire Entanglement](#alpinejs-livewire-entanglement)
4. [PHP-Side: Validation and Casting](#php-side-validation-and-casting)
5. [Package Auto-Discovery](#package-auto-discovery)
6. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/743/8998fac3a41451ab3fe1588194e17a43.png) Filament · 3 min read### Securing Filament Plugins with Plumb: Automated Security Scoring for PHP Packages

5 Oct 2026 ](https://www.msaied.com/public/articles/securing-filament-plugins-with-plumb-automated-security-scoring-for-php-packages) [ ![](https://cdn.msaied.com/742/2d02018669cdeedccb5de2efb898f0ee.png) Filament · 3 min read### Filament v3.3.56 Released: File Hash Names and Livewire Upload Fix

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v3356-released-file-hash-names-and-livewire-upload-fix) [ ![](https://cdn.msaied.com/741/5b55c123ad08e4d34e1f4b99ad6a428b.png)  · 3 min read### Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-5) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
