swisnl/filament-geometry
A Filament form field for drawing and editing map geometries. Let users place points, draw lines, and outline polygons on an interactive map, then save the result as GeoJSON in your database. Adds spatial input to your admin panel with simple, familiar field configuration and no custom JavaScript re...
swisnl/filament-geometry adds a map-backed geometry field to Filament forms: the user draws a point, line, or polygon on a Leaflet map, and the field hands back GeoJSON you can persist in a JSON column. Version 0.8.1 is the first release with Filament 5.x compatibility, so it is the minimum you should pin on a Filament 5 app.
composer require swisnl/filament-geometry:^0.8.1
If the map renders blank after install, it is almost always assets rather than code: run php artisan filament:assets, then php artisan view:clear, and hard-refresh the browser. If you are on Filament 4.x, 0.8.0 is functionally equivalent — upgrade anyway so you stay on one constraint when you move to 5.
Where to look first:
vendor/swisnl/filament-geometry/src — the field class and its Blade view; this is where the map options, Leaflet init, and state hydration live.First use case — a "draw the area" field on a resource:
use SwisNl\FilamentGeometry\Forms\Components\Geometry;
Geometry::make('area')
->label('Service area')
->polygon()
->required(),
// migration
$table->json('area')->nullable();
// model
protected $casts = ['area' => 'array'];
Render it, save it, and confirm the value round-trips through the edit form before you build anything else on top.
Restrict the geometry type per field. Most real applications want exactly one shape kind per field — a delivery zone is a polygon, a pickup point is a point. Use the field's type helpers rather than accepting everything and validating afterwards; you get a better editing UX and simpler stored data.
Normalise the stored shape. Decide early whether you persist a bare geometry ({"type":"Polygon","coordinates":[...]}) or a full Feature. A Feature with a properties bag is far more flexible (name, colour, metadata) and costs nothing extra. Wrap the field in a small accessor or custom cast so every consumer of the model sees the same structure.
Map defaults live in code, not in the DB. Centre, zoom, and tile layer are field configuration. Put your default centre/zoom in a config file or a small helper so every geometry field in the app is consistent, and override per-field only where the geography genuinely differs (e.g. a regional office map vs. a nationwide one).
Table/infolist display. Geometry is hard to read as raw JSON in a table. Either render a compact map in an infolist entry, or show a human summary (point count, area, centroid) computed from the GeoJSON and keep the map for the view page. Do not dump the raw array into a text column — it will destroy table layout.
Spatial queries need a second column. GeoJSON in a JSON column is a blob: ST_Contains, ST_Intersects, and bounding-box indexes will not help you. If you need "which zone contains this lat/lng?", maintain a parallel native geometry/geography column (MySQL 8 or Postgres + PostGIS) written from the same form data — via a model observer or a saving hook — and query that. Keep the JSON as the source of truth for the editor and the spatial column as the index.
Testing. In a Livewire/Filament form test, set the field state to a fixture GeoJSON array and assert persistence and validation. Keep a handful of fixtures in tests/Fixtures (a point, a simple polygon, a self-intersecting polygon) — they double as regression data if the field's state format ever changes between 0.x releases.
Seeding and imports. When you seed demo data or import shapes from another system, write through the same normalisation path the field produces. Imported geometries that skip that step are the number one source of "the map shows nothing but the data looks fine" bugs.
>= 0.8.1. This release exists solely to add that compatibility. If you are on Filament 5 and pinned 0.8.0, composer may resolve but the field will break at render time. Bump the constraint, then run php artisan filament:assets — the compiled map assets from the older build stay in public/ and can mask or cause the upgrade.~0.8.1 and read every changelog; the Filament major-version jump in a patch release is the pattern to expect.[longitude, latitude]; Leaflet is [latitude, longitude]. Every hand-written fixture, every import mapping, and every "my marker is in the ocean off Africa" report comes from this. Convert in exactly one place.0x0 and render grey. If you hit it, the usual fix is triggering invalidateSize() on the map after the container becomes visible — check whether the field's view already does this before patching it, and if not, this is a legitimate extension point (publish the view and add it).img-src for your tile host and may need style-src 'unsafe-inline' for Leaflet's positioning. In dark-mode admin panels the map will be a bright rectangle — a CSS filter on the tile layer is the cheap fix.null or {}/[] and enforce it, otherwise your spatial sync and your validation rules will disagree about whether the record has geometry.$wire.get('data.area')) rather than the rendered DOM — it separates "the field didn't capture the drawing" from "the map didn't render", which are entirely different problems in this package.invalidateSize(). Prefer that over subclassing the field, so package upgrades stay reviewable.How can I help you explore Laravel packages today?