zielu92/filament-image-labeler
Filament Image Labeler adds an interactive image labeling field to Filament forms, letting users draw or place labels directly on uploaded images. Label data is stored with your models for easy editing and display in resources.
Install and register
composer require zielu92/filament-image-labeler:^0.5
php artisan vendor:publish --tag="filament-image-labeler-config" # if you need to tweak defaults
php artisan migrate # only if the package ships its own tables
Register the plugin on your panel if the package exposes one (->plugin(FilamentImageLabelerPlugin::make()) in your PanelProvider), otherwise the field works standalone inside any form schema.
Look here first
config/filament-image-labeler.php — storage disk, image sizes, annotation defaults.Zielu92\FilamentImageLabeler\Fields\ImageLabeler or similar) — the fluent API is the real documentation.resources/boost/ in the package vendor/source tree — v0.5.0 ships a guideline plus the filament-image-labeler-development skill, which is the most accurate description of the field API and syncAnnotations() contract available today.Opt into Laravel Boost (new in 0.5.0)
php artisan boost:install # tick filament-image-labeler in the package list
# or, on an existing Boost install:
php artisan boost:update --discover
After that your coding agent gets an always-on guideline plus the on-demand skill. This is the fastest way to get correct boilerplate for the field, so do it before writing your first form.
First use case — annotate an uploaded image and store the resulting labels/bounding boxes:
use Zielu92\FilamentImageLabeler\Fields\ImageLabeler;
ImageLabeler::make('image')
->label('Reference image')
->annotations('annotations') // JSON column or relation
->syncAnnotations();
Then render the stored annotations back on the edit form and expose them read-only on the view/infolist.
Form + persistence split
Treat the field as two concerns: the widget renders/edits annotations, syncAnnotations() writes them. Keep the field in the schema, and let the sync hook fire in the create/update lifecycle rather than calling it manually from your handleRecordUpdate — the contract is designed for that. If you need custom persistence (separate table, audit log, queued processing), disable the built-in sync and reuse the field's annotation payload in your own afterCreate/afterSave callback.
Relation-backed annotations
The most maintainable pattern for anything you'll query or report on:
Image model has annotations() (HasMany) or a JSON annotations column with an array cast.syncAnnotations() reconcile create/update/delete on save, so re-editing an image doesn't duplicate rows.Use a plain JSON column when annotations are purely presentational; switch to a relation as soon as you need filtering ("all images containing label X") or per-annotation metadata.
Entity links
Entity links let an annotation point at another model (a product, a defect code, a person). Pattern: seed the linkable options from a scoped query in the resource form, not from a global list, so the picker stays small. Persist the morph key/class through the field so links survive renames of the display label.
Automatic annotation
Use automatic annotation for pre-fill: run it on upload to get a first-pass set of boxes, then let the human correct them in the same field. Keep the auto-generated result flagged (e.g. a source column) so downstream consumers can distinguish machine from human labels, and never overwrite human edits on re-run.
Agent-assisted development (0.5.0)
With Boost installed, the skill covers the field API and the syncAnnotations() contract in context. Day-to-day this means:
boost.json so the whole team's agents use the same guidance.Testing
Filament form fields are easiest to test through Livewire component tests (fillForm/call('save')) rather than unit-testing the field directly. Assert on the persisted rows after save, not on the field's internal state — that's the behaviour syncAnnotations() guarantees.
syncAnnotations() needs a persisted record. On create, the parent model must have an ID before annotations can be attached. Use the after-create hook, or call sync explicitly post-save(); calling it in beforeCreate/mutateFormDataBeforeCreate will fail or silently drop annotations.
Boost version floor. The shipped skill requires Boost 2.x. On Boost 1.x you still get nothing from boost:install — upgrade Boost first, then re-run boost:update --discover. Discovery is not automatic on package update: after bumping this package you must re-run discovery or the agent keeps the old (or no) guideline.
Coordinate normalisation. Annotation boxes are typically stored relative to the rendered image size. If you change the image width/height config or the displayed dimensions between capture and re-render, existing boxes drift. Store normalised coordinates (0–1 or percentages) and pick one convention project-wide; check what the field emits before writing your own renderer.
Disk and visibility. Uploaded images go to whatever disk the field configures. On non-public disks (S3 private, local storage/app/private), make sure your preview/URL generation uses temporary URLs — a mismatch here looks like "the annotator shows a blank canvas".
Array casts. A JSON annotations column needs protected $casts = ['annotations' => 'array'] (or AsArrayObject). Without it you get string payloads into the field on edit and the canvas renders empty even though the DB looks right.
Reordering/replacing the image. If a user swaps the image but keeps annotations, decide explicitly whether annotation sets are cleared, remapped, or preserved. Default to clearing — stale boxes on a new image are worse than redoing the work — and make it a visible confirm in the UI.
Extension points. Prefer the field's fluent configuration and the package config file over subclassing the field. Subclassing ties you to internal method signatures that the 0.x releases still move; if you must extend, isolate it in one custom field class so a future 0.6 bump is a single-file change.
0.x churn. The package is pre-1.0: pin the constraint (^0.5) and read the release notes for each minor. The 0.5.0 release itself is additive only (Boost resources, no API or config changes), so upgrading from 0.4.x is safe.
How can I help you explore Laravel packages today?