darkclow4 / filament-map-picker
Leaflet map picker and GeoJSON draw field for Filament 5.
Package info
github.com/darkclow4/filament-map-picker
Language:JavaScript
pkg:composer/darkclow4/filament-map-picker
Requires
- php: ^8.3
- filament/filament: ^5.0
- illuminate/contracts: ^13.0
- spatie/laravel-package-tools: ^1.92
Suggests
- ext-json: Required for working with JSON state and custom tile configuration.
This package is not auto-updated.
Last update: 2026-07-29 08:19:07 UTC
README
filament-map-picker provides Filament 5 form, infolist, and widget components powered by Leaflet.js. Use it to select a point location in forms, draw one or more map areas as GeoJSON, and display saved locations in read-only infolists or dashboard widgets.
The package is designed to stay flexible. It does not force how you persist latitude, longitude, or area data. Instead, it keeps a predictable field state and lets you map the values however you want through afterStateUpdated() and afterStateHydrated().
Features
- Built for Filament 5
- Powered by locally bundled Leaflet.js and Leaflet Draw
- Form field, infolist entry, and dashboard widget APIs
- Default OpenStreetMap tile layer
- Custom tile support through
->tiles([...]) dragmode andclickmode for the point marker- Optional geocoding search UI
- Optional polygon, rectangle, and circle drawing tools
- Optional multi-shape GeoJSON collections
- Combined point + area state in a single field
- Reactive Livewire-friendly state updates
- Alpine.js powered interaction
- Leaflet layers control for switching tile layers
- Read-only map rendering for detail pages and dashboards
Requirements
- PHP 8.3+
- Laravel 13+
- Filament 5
Installation
Install the package with Composer:
composer require darkclow4/filament-map-picker
Then publish Filament assets:
php artisan filament:assets
Package Registration
The package supports Laravel auto-discovery through:
Darkclow4\FilamentMapPicker\FilamentMapPickerServiceProvider::class
In most applications, no manual provider registration is needed.
State Format
Point-only mode
By default, the field stores point coordinates as:
[
'lat' => -6.2,
'lng' => 106.816666,
]
Point + draw mode
When draw mode is enabled, the field stores a combined state:
[
'lat' => -6.2,
'lng' => 106.816666,
'geojson' => [
'type' => 'FeatureCollection',
'features' => [
// polygon / rectangle / circle features
],
],
]
This makes it easy to keep a marker location and one or more service-area shapes in the same field while still saving them into separate database columns.
Basic Usage
Point-only form field
use Darkclow4\FilamentMapPicker\Forms\Components\MapPicker; use Filament\Schemas\Components\Utilities\Set; MapPicker::make('location') ->mode('click') ->defaultLocation(-6.2, 106.816666) ->zoom(13) ->height(420) ->live() ->afterStateUpdated(function (?array $state, Set $set): void { $set('latitude', $state['lat'] ?? null); $set('longitude', $state['lng'] ?? null); });
Point + area in a single field
use Darkclow4\FilamentMapPicker\Forms\Components\MapPicker; use Filament\Forms\Components\Hidden; use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Utilities\Set; MapPicker::make('location') ->mode('drag') ->defaultLocation(-6.2, 106.816666) ->drawable() ->drawTools(['polygon', 'rectangle', 'circle']) ->multipleShapes() ->fitDrawBounds(false) ->height(480) ->live() ->afterStateHydrated(function (MapPicker $component, Get $get): void { $component->state([ 'lat' => is_numeric($get('latitude')) ? (float) $get('latitude') : -6.2, 'lng' => is_numeric($get('longitude')) ? (float) $get('longitude') : 106.816666, 'geojson' => is_array($get('area')) ? $get('area') : [ 'type' => 'FeatureCollection', 'features' => [], ], ]); }) ->afterStateUpdated(function (?array $state, Set $set): void { $set('latitude', $state['lat'] ?? null); $set('longitude', $state['lng'] ?? null); $set('area', $state['geojson'] ?? [ 'type' => 'FeatureCollection', 'features' => [], ]); }); Hidden::make('area');
Read-only infolist entry
Use MapEntry when you want to display saved coordinates on view/detail pages.
Add ->interactive() if you want the user to pan and zoom around the point without changing the saved state.
use Darkclow4\FilamentMapPicker\Infolists\Components\MapEntry; MapEntry::make('location') ->state(fn ($record): array => [ 'lat' => (float) $record->latitude, 'lng' => (float) $record->longitude, ]) ->interactive() ->height(360) ->zoom(15);
For saved GeoJSON areas, enable ->drawable() and include geojson in the state:
use Darkclow4\FilamentMapPicker\Infolists\Components\MapEntry; MapEntry::make('service_area') ->label('Service Area') ->drawable() ->interactive() ->showAreaMeasurement() ->areaMeasurementUnit('ha') ->state(fn ($record): array => [ 'lat' => (float) $record->latitude, 'lng' => (float) $record->longitude, 'geojson' => $record->service_area_geojson ?? [ 'type' => 'FeatureCollection', 'features' => [], ], ]) ->height(420);
Dashboard map widget
Use MapWidget directly when the state is passed from a page or dashboard:
use Darkclow4\FilamentMapPicker\Widgets\MapWidget; protected function getHeaderWidgets(): array { return [ MapWidget::make([ 'heading' => 'Main Store', 'description' => 'Latest saved branch location', 'state' => [ 'lat' => -6.2, 'lng' => 106.816666, ], ]), ]; }
For dynamic configuration, create your own widget class and extend the package widget:
namespace App\Filament\Widgets; use Darkclow4\FilamentMapPicker\Widgets\MapWidget; class BranchMapWidget extends MapWidget { public ?string $heading = 'Branch Coverage'; public ?string $description = 'Primary operating area'; public function mount(): void { $this ->drawable() ->showAreaMeasurement() ->areaMeasurementUnit('ha') ->height(460) ->zoom(12); $this->state = [ 'lat' => -6.2, 'lng' => 106.816666, 'geojson' => [ 'type' => 'FeatureCollection', 'features' => [], ], ]; } }
Selection Modes
Drag mode
MapPicker::make('location')->mode('drag')
- the user drags the map
- the marker stays centered
- coordinates are taken from the map center
Click mode
MapPicker::make('location')->mode('click')
- the user clicks on the map
- the marker moves to the clicked point
- coordinates are taken from that point
Default Tile Layer
If you do not provide custom tiles, the field uses OpenStreetMap automatically.
MapPicker::make('location') ->tile('osm')
Built-in default tile definition:
[
'osm' => [
'label' => 'OpenStreetMap',
'url' => 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
'options' => [
'maxZoom' => 19,
'attribution' => '© OpenStreetMap contributors',
],
],
]
Custom Tile Layers
You can provide your own tile layers with ->tiles([...]). Those definitions are serialized to JSON and passed directly to Leaflet.
MapPicker::make('location') ->tiles([ 'opentopo' => [ 'label' => 'Open Topo Map', 'url' => 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', 'options' => [ 'maxZoom' => 19, 'attribution' => 'Map data: © OpenStreetMap contributors | Map style: © OpenTopoMap', ], ], ]) ->tile('opentopo');
Tile behavior:
- if
->tiles()is not called, onlyosmis available - if
->tile()is not called,osmis selected by default - if the selected tile key is invalid, the field falls back to
osm - if more than one base layer exists, Leaflet's built-in layers control is shown on the map
- by default the built-in
osmtile is always included - use
->showDefaultTile(false)if you want to hide the built-inosmtile and only expose your custom tiles
Searchable Mode
Enable geocoding search when you want users to jump quickly to a location:
MapPicker::make('location') ->searchable() ->searchPlaceholder('Search for a place') ->searchResultLimit(8)
Behavior:
- users can search for a place or address
- matching results appear in a dropdown list
- selecting a result recenters the map and updates the field state
- selected results are highlighted when reopened
By default, search uses:
https://nominatim.openstreetmap.org/search
You can override it:
->searchProviderUrl('https://nominatim.openstreetmap.org/search')
Drawing Shapes as GeoJSON
Enable draw mode when you want the user to define areas while keeping a marker location in the same field state.
MapPicker::make('location') ->drawable() ->drawTools(['polygon', 'rectangle', 'circle']) ->multipleShapes() ->fitDrawBounds(false)
Notes:
- draw mode stores the field state as
['lat', 'lng', 'geojson'] - the marker location still works as normal
- the drawn area is stored inside
geojson - by default only one drawn shape is kept at a time
- use
->multipleShapes()if you want multiple polygons / rectangles / circles in the sameFeatureCollection - editing or deleting shapes updates the field state automatically
- use
afterStateUpdated()/afterStateHydrated()to maplatitude,longitude, andgeojsoninto separate database columns
API Reference
Unless noted otherwise, these configuration methods are available on:
Darkclow4\FilamentMapPicker\Forms\Components\MapPickerDarkclow4\FilamentMapPicker\Infolists\Components\MapEntryDarkclow4\FilamentMapPicker\Widgets\MapWidget
->tile(string $tile)
Sets the active tile key.
->tile('osm')
->tiles(array $tiles)
Registers custom tile layers.
Expected tile format:
[
'custom-key' => [
'label' => 'Tile Name',
'url' => 'https://example.com/{z}/{x}/{y}.png',
'options' => [
'maxZoom' => 20,
'attribution' => '...'
],
],
]
->showDefaultTile(bool $condition = true)
Controls whether the built-in osm tile should be included in the available tile list.
->showDefaultTile(false)
->interactive(bool $condition = true)
Allows a read-only map to remain draggable and zoomable without syncing state back to Livewire.
->interactive()
->mode('drag|click')
Defines the point marker interaction mode.
->mode('drag') ->mode('click')
->defaultLocation(float $lat, float $lng)
Sets the initial map coordinates.
->defaultLocation(-6.2, 106.816666)
->zoom(int $level)
Sets the initial map zoom level.
->zoom(13)
->height(int|string $height)
Sets the map container height.
->height(420) ->height('32rem') ->height('60vh')
->markerColor(string $color)
Sets the marker color used in both drag and click modes.
->markerColor('#0f766e') ->markerColor('rgb(37, 99, 235)')
->searchable(bool $condition = true)
Enables place search. This is only available on the form field.
->searchable()
->searchPlaceholder(string $placeholder)
Overrides the search input placeholder.
->searchPlaceholder('Search for a city or address')
->searchProviderUrl(string $url)
Overrides the geocoding endpoint used by the search box.
->searchProviderUrl('https://nominatim.openstreetmap.org/search')
->searchResultLimit(int $limit)
Controls how many search results are shown in the dropdown.
->searchResultLimit(8)
->drawable(bool $condition = true)
Enables shape drawing tools and switches the field state into combined point + GeoJSON mode.
->drawable()
->drawTools(array $tools)
Controls which draw tools are available. Supported values are polygon, rectangle, and circle.
->drawTools(['polygon', 'rectangle'])
->fitDrawBounds(bool $condition = true)
Controls whether the map should automatically fit to the drawn shapes after create or edit.
->fitDrawBounds(false)
->multipleShapes(bool $condition = true)
Allows the field to keep multiple drawn shapes inside the GeoJSON FeatureCollection.
->multipleShapes()
->showAreaMeasurement(bool $condition = true)
Shows area labels directly inside the map for each drawn shape when draw mode is enabled.
->showAreaMeasurement()
->areaMeasurementUnit('m2'|'ha')
Controls the unit used for the displayed area measurement.
->areaMeasurementUnit('m2') ->areaMeasurementUnit('ha')
Notes
- The package does not force any persistence strategy for
latitude,longitude, or area GeoJSON - You are free to map the field state into any schema structure you prefer
- Custom tiles are the developer's responsibility, including availability and usage terms
- Search providers are the developer's responsibility, including availability and usage terms
License
This package is open-sourced software licensed under the MIT license.