Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Map Picker Laravel Package

doode/filament-map-picker

A Filament form field that lets users pick a location on an interactive map, saving latitude and longitude directly to your model. Drop it into any resource form for fast, accurate address or coordinate selection with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose Filament form field: it renders a Leaflet map with Leaflet-Geoman draw controls and stores the picked geometry (marker, polyline, polygon, rectangle, circle) as GeoJSON-ish state. As of v1.6.5 the package is explicitly supported on Laravel 10, 11 and 12 and ships a test suite, so it's a safer bet than its star count suggests — but the source is still effectively the documentation. Budget an hour in src/ and resources/js before committing.

composer require doode/filament-map-picker
php artisan vendor:publish --tag="filament-map-picker-assets"   # tag name may differ - check README

Laravel ≠ Filament. v1.6.5 nails down the Laravel constraint, but the thing that will actually break your install is the Filament major version — v3 and v4 field APIs diverge. Read the filament/filament constraint in the package's composer.json before installing, and pin the package version.

If the package ships a plugin class, register it in your panel provider (->plugin(MapPickerPlugin::make())); if it is a pure form component (common for map pickers), skip this and just use the field.

First use case: a Venue resource where an admin drops a pin instead of typing lat/lng.

use Doode\FilamentMapPicker\Forms\Components\MapPicker; // confirm namespace in src/

MapPicker::make('location')
    ->label('Location')
    ->setLocation(51.5074, -0.1278)   // initial centre / pin
    ->zoom(13)
    ->height(400)
    ->required(),

Store it as JSON:

protected $casts = ['location' => 'array'];

Where to look first: the field's src/Forms/Components/*.php (what public setters exist), the Blade view + Alpine component (how state is written back to Livewire), and the JS bundle (which Geoman modes are actually enabled). That trio answers 90% of questions. The new tests/ directory is a decent fourth stop — it shows the exact payload shape the field hydrates and dehydrates, which is faster than reverse-engineering the Alpine.

Implementation Patterns

Point-only vs. shape. For a simple address pin, leave draw controls off and read $state['lat'] / $state['lng'] (verify the exact key names against the JS). For service areas, delivery zones, or catchments, enable the drawing control, constrain it to one shape mode, and store the resulting GeoJSON feature collection directly in a json column.

Denormalise for queries. Never query against the JSON blob. In mutateFormDataBeforeSave() (or afterStateUpdated) extract what you need into real columns:

->mutateFormDataBeforeSave(function (array $data) {
    $geo = $data['location'] ?? null;
    $data['lat'] = $geo['lat'] ?? null;
    $data['lng'] = $geo['lng'] ?? null;
    return $data;
})

Add a MySQL spatial index / POINT column only if you actually need radius queries; otherwise plain decimal(10,7) columns plus a bounding-box whereBetween will carry you a long way.

Validate the shape, not just presence. ->required() passes on an empty array in some Filament versions. Add a closure rule that asserts the geometry type and a sane coordinate range (-90..90, -180..180) before it hits the database.

Repeater / multi-location. Each map instance needs a unique wire key. Test this early — nested map pickers inside a Repeater are the most common place these packages break, because the Leaflet instance isn't torn down on row deletion. The v1.6.5 tests are PHP-side and won't catch this; you have to click it yourself.

Read-only display. There is generally no infolist entry. For view pages, either a TextEntry with the coordinates plus a static-map image, or embed a <div> with Leaflet in a custom Blade view. Don't try to reuse the form component in read-only mode unless the package explicitly supports it.

Geocoding is your job. The picker gives you coordinates from clicks and dragged pins; it does not do address search. Wire a Nominatim/Mapbox/Google lookup into a sibling TextInput with ->live(debounce: 500) and ->afterStateUpdated(), then push the result into the map field with ->setLocation() on the parent form's ->fill(), or use $set('location', [...]) from the search field.

Saving GeoJSON for later rendering. If you need to render the zone back on a public-facing map, store the raw GeoJSON string and feed it straight to Leaflet or Mapbox GL on the front end — don't round-trip it through your own model.

Borrow from the test suite. v1.6.5 added tests and QA passes. Rather than guessing what array shape the field emits, copy a fixture out of tests/ into your own Pest/PHPUnit test and assert your mutateFormDataBeforeSave() handles it. That gives you a regression net for free across future upgrades.

Gotchas and Tips

  • GeoJSON is [lng, lat], not [lat, lng]. This is the single most common bug. Leaflet's own LatLng and GeoJSON's coordinate array use opposite orders. Put a test around any conversion.
  • Assets must be published and re-published on upgrade. A blank grey box with no tiles is almost always a missing Leaflet CSS file, not a JS error. Check the browser network tab for 404s on leaflet.css / geoman. Every release — including patch releases like 1.6.5 — is a candidate for stale published assets; re-run vendor:publish --force when the map behaves oddly after composer update.
  • Maps inside modals, tabs, and wizard steps render at zero height. Leaflet computes size on init. If the container is hidden, tiles load as a single grey square. Fix by calling map.invalidateSize() when the parent becomes visible, or set an explicit pixel height via ->height().
  • Livewire re-renders can orphan the map. Wrap the map container in wire:ignore if you patch the view; most implementations do this already, but verify after any Filament upgrade.
  • Debounce state updates. Dragging a pin fires continuously. If ->live() is on, you'll round-trip to the server on every frame. Use ->live(debounce: 500) or ->dehydrated(false) with a deferred save.
  • Tile provider policy. Default OpenStreetMap tiles are fine for dev but violate the tile usage policy at production scale, and OSM has no SLA. Swap in MapTiler, Stadia, or a self-hosted provider via the field's tile config.
  • Check the Leaflet-Geoman licence before shipping. Geoman's free tier has changed licence terms historically; if you're building a commercial product, read the actual package.json in the published assets rather than trusting the repo's top-level MIT badge (which may only cover the PHP wrapper).
  • Nullability. Decide early whether "no location" is null or [] — the two behave differently through ->required() and through your cast. Normalise in mutateFormDataBeforeSave.
  • Read the new tests rather than trusting the badge. v1.6.5's QA was AI-assisted, which often means broad but shallow coverage. Expect the suite to cover PHP-side field behaviour (config, hydration, dehydration) — not browser rendering, Geoman drawing, or Livewire-in-Repeater teardown. Green CI is not evidence your specific integration works.
  • Extension points are still thin. Expect to publish the views and edit Blade/Alpine directly rather than relying on a rich PHP API. Fork-and-patch remains a realistic maintenance posture; pin the exact version in composer.json ("doode/filament-map-picker": "1.6.5") and add an integration test that asserts the field hydrates and dehydrates a known payload, so a future minor bump can't silently change the state shape on you.
  • CSP and HTTPS. If you run a Content-Security-Policy, you'll need img-src and connect-src allowances for the tile host. On HTTPS pages, any http:// tile URL silently fails.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource