eg-mohamed/filament-map-picker
Install and publish assets.
composer require eg-mohamed/filament-map-picker
php artisan vendor:publish --tag="filament-map-picker-config" # optional
php artisan filament:assets # required — the map JS/CSS ships as a Filament asset
If your panel registers plugins explicitly, add the package plugin to the panel provider; otherwise the form component can be used directly.
Where to look first.
setCenterLocation, showMarker, draggable, tilesUrl).config/filament-map-picker.php after publishing: default centre coordinates, zoom range, tile URL/attribution, and any API key slot.First use case. An address/location picker on a Resource form. Add two decimal columns (latitude, longitude, precision 10,7 / 10,7) via migration, then drop the field into the form schema. The component's state is typically an array of lat/lng keys, so hydrate it in mutateFormDataBeforeFill() / mutateFormDataBeforeCreate() rather than trying to bind it straight to a column.
1.0.1 release: patch bump with an empty changelog (compare link only). No documented features, deprecations, or BC breaks — a low-risk upgrade from 1.0.0. Skim the GitHub compare diff before bumping in production, since silent patches to JS init code are exactly the kind of change that breaks map rendering without a version-visible reason.
Form + persistence. Keep the map field dehydrated into a virtual state and split it into real columns:
MapPicker::make('location')
->label('Pick a location')
->showMarker()
->draggable()
->clickable()
->live()
->afterStateUpdated(function ($state, Set $set) {
$set('latitude', $state['lat'] ?? null);
$set('longitude', $state['lng'] ?? null);
})
->dehydrated(false),
Hidden::make('latitude'),
Hidden::make('longitude'),
Hydrate location from the model in mutateFormDataBeforeFill() with ['lat' => $record->latitude, 'lng' => $record->longitude]. Hidden inputs must be present in the schema — Filament only saves fields it knows about.
Reactive two-way binding. Use ->live() on the map and on plain TextInputs for lat/lng if you want users to be able to type coordinates and move the marker. Guard against loops by only $set()-ing when the value actually changed.
Validation. Validate in rules() on the hidden fields, not the map field: ['required', 'numeric', 'between:-90,90'] and ['required', 'numeric', 'between:-180,180']. Add a Rule::requiredIf on the map state if the location is mandatory, otherwise a submitted form with an untouched map quietly persists null.
Table + infolist display. A TextColumn rendering lat/lng as a Google Maps / OpenStreetMap deep link is cheaper than embedding a map in a table row:
TextColumn::make('latitude')
->formatStateUsing(fn ($state, $record) => "{$record->latitude}, {$record->longitude}")
->url(fn ($record) => "https://www.google.com/maps?q={$record->latitude},{$record->longitude}", true)
->openUrlInNewTab(),
Multiple maps on one page. Give each instance a unique statePath/key; duplicate container IDs are a common source of "second map is dead" bugs.
Testing. Drive it through fillForm(['latitude' => ..., 'longitude' => ...]) and assert the DB columns, rather than trying to simulate marker drags in a browser. Keep a small Dusk/browser test for the map-render path only.
php artisan filament:assets after every upgrade) or a blocked tile server. Check the browser network tab for 404s on the package JS before touching config.Hidden tab, a wizard step, or a collapsed section and Leaflet initialises at 0px. Fix with an explicit ->height(...)/CSS min-height, or call map.invalidateSize() on the tab/wizard change event.@destroy, and version-pin the package if you see regressions — this is the area 1.0.1 most plausibly touches given the empty changelog.float columns plus JS number round-tripping will drift coordinates. Use decimal(10,7) and cast to string or decimal:7 in the model.tilesUrl at a paid/commercial provider or self-hosted tiles, and keep the attribution string intact. Air-gapped deployments need a self-hosted tile source or this component is unusable.config/services.php/.env, never in the panel provider, and restrict it by HTTP referrer.Content-Security-Policy silently breaks tiles and marker icons. Whitelist the tile domain under img-src and connect-src.->view('vendor/filament-map-picker/...') after publishing views; that's the supported escape hatch for custom marker icons, autocomplete search boxes, or geocoding on click (call your geocoder in afterStateUpdated and $set() the address fields).composer.json ("1.0.1"), read the compare diff on each bump, and keep a smoke test that asserts the form renders and the component class resolves.How can I help you explore Laravel packages today?