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

humaidem/filament-map-picker

Filament Map Picker provides an interactive map form field for Laravel Filament apps. Users can search, drop a pin, and save latitude/longitude coordinates, making location selection simple in resources and forms.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require humaidem/filament-map-picker

Optionally publish the config to set global defaults (tile URL, zoom, marker/draggable/clickable behavior):

php artisan vendor:publish --tag="filament-map-picker-config"

The field ships a Blade view plus Leaflet assets and is auto-discovered by Filament v3 — no plugin registration in your PanelProvider is normally required. If the map renders as an unstyled grey box, that's the first sign the Leaflet CSS/JS didn't load; re-check asset publishing and clear php artisan filament:cache-components.

Storage

The field's state is a PHP array shaped like ['lat' => -6.2, 'lng' => 106.816666]. The simplest schema is a single JSON column:

$table->json('location')->nullable();

…and a cast on the model:

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

First use case

Drop it into an existing Resource form and store a point on a record:

use Humaidem\FilamentMapPicker\Fields\MapPicker;

MapPicker::make('location')
    ->label('Location')
    ->defaultLocation([-6.2, 106.816666])   // [lat, lng]
    ->showMarker()
    ->draggable()
    ->extraAttributes(['style' => 'min-height: 400px']);

Before wiring it up, the first thing to inspect is the actual state shape your version produces: add ->afterStateHydrated(fn ($component, $state) => logger($state)) and load an edit page. Older/newer tag combinations have differed on whether state arrives as an array or a "lat,lng" string, and everything downstream depends on knowing which one you have.

Implementation Patterns

Two-column mapping (lat/lng in separate columns)

If your schema has latitude / longitude, adapt at the boundaries rather than changing the field:

MapPicker::make('location')
    ->afterStateHydrated(function (MapPicker $component, $record) {
        if ($record?->latitude && $record?->longitude) {
            $component->state([
                'lat' => $record->latitude,
                'lng' => $record->longitude,
            ]);
        }
    })
    ->dehydrated(false)   // handled manually below

…then in the page/resource:

protected function mutateFormDataBeforeSave(array $data): array
{
    $data['latitude']  = $data['location']['lat'] ?? null;
    $data['longitude'] = $data['location']['lng'] ?? null;
    unset($data['location']);
    return $data;
}

Reverse geocoding on pick

The package has no geocoder, so hook Livewire state updates and call your provider (Nominatim, Google Geocoding, Mapbox) yourself:

MapPicker::make('location')
    ->live()
    ->afterStateUpdated(function ($state, Set $set) {
        if (! is_array($state)) return;
        $response = Http::get('https://nominatim.openstreetmap.org/reverse', [
            'lat' => $state['lat'], 'lon' => $state['lng'], 'format' => 'json',
        ])->json();
        $set('address', $response['display_name'] ?? null);
    });

Debounce the calls (->debounce(800)) and cache results — Nominatim's usage policy is strict.

Table and infolist display

The stored array isn't human-readable, so format the column yourself and link out:

Tables\Columns\TextColumn::make('location')
    ->formatStateUsing(fn ($state) => $state
        ? "{$state['lat']}, {$state['lng']}"
        : '—')
    ->url(fn ($record) => $record->location
        ? "https://www.openstreetmap.org/?mlat={$record->location['lat']}&mlon={$record->location['lng']}#map=18"
        : null)
    ->openUrlInNewTab();

Standardize across panels

Because the field has many per-instance options (zoom, maxZoom, minZoom, tilesUrl, showMarker, draggable, clickable, showMyLocationButton), wrap it in your own subclass so app-wide conventions live in one place:

// app/Filament/Fields/LocationPicker.php
class LocationPicker extends MapPicker
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->showMarker()->draggable()->zoom(16)
             ->extraAttributes(['style' => 'min-height: 380px; border-radius: .5rem']);
    }
}

Repeater / Tab / Wizard contexts

Maps inside hidden containers initialize at zero height. Keep the map in the initially-visible tab, or trigger a resize from Alpine on tab change; wire:ignore on the wrapper prevents Livewire from tearing down the Leaflet instance during unrelated re-renders.

Gotchas and Tips

  • defaultLocation() and state have different shapes. defaultLocation([lat, lng]) is a numeric pair; the state is an associative array. Mixing them up silently places the marker at null, null.
  • OSM default tiles are not a production plan. OpenStreetMap's tile servers forbid heavy/commercial traffic. Swap tiles_url in the published config for MapTiler, Mapbox, Carto, or your own tile server before launch.
  • No search / address box. Users can only drag or click. If "find a place by name" is a requirement, plan for a separate search input that calls Nominatim/Photon and pushes coordinates into the field, or evaluate a heavier package (cheesegrits/filament-google-maps, dotswan/filament-map-picker).
  • Dark mode and styling. Map tiles stay light regardless of Filament's theme. Either configure a dark tile set or use a CSS filter on the map container via extraAttributes(['class' => '...']).
  • showMyLocationButton() requires HTTPS. Browser geolocation fails silently on http:// local hosts; test behind valet share/ngrok or a proper cert.
  • Validation. A ->required() on the field still passes if the array contains ['lat' => null, 'lng' => null]. Add ->rules(['array:lat,lng']) plus a custom rule that both values are numeric and within bounds.
  • Maintenance risk. Last tagged release is November 2023, ~59 stars, zero dependents. Pin the version in composer.json, test carefully through Filament minor upgrades, and treat Filament v4 as unverified until you've checked the field yourself. For a core feature, a small fork (or a thin wrapper around Leaflet in your own Blade view via getView()) is a reasonable hedge.
  • Debugging checklist when the map is blank: (1) Leaflet CSS present in the network tab, (2) Leaflet is not defined in console, (3) container min-height set, (4) state shape logged via afterStateHydrated, (5) tile URL reachable (CSP img-src/connect-src headers in production).
  • Extension points. Publish the config for global defaults, subclass the field for per-app conventions, and override getView() if you need extra UI (search box, "clear location", coordinate readout).
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource