darkclow4/filament-map-picker

Leaflet map picker and GeoJSON draw field for Filament 5.

Maintainers

Package info

github.com/darkclow4/filament-map-picker

Documentation

Language:JavaScript

pkg:composer/darkclow4/filament-map-picker

Transparency log

Statistics

Installs: 349

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

v5.2.0 2026-07-29 08:10 UTC

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([...])
  • drag mode and click mode 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, only osm is available
  • if ->tile() is not called, osm is 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 osm tile is always included
  • use ->showDefaultTile(false) if you want to hide the built-in osm tile 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 same FeatureCollection
  • editing or deleting shapes updates the field state automatically
  • use afterStateUpdated() / afterStateHydrated() to map latitude, longitude, and geojson into separate database columns

API Reference

Unless noted otherwise, these configuration methods are available on:

  • Darkclow4\FilamentMapPicker\Forms\Components\MapPicker
  • Darkclow4\FilamentMapPicker\Infolists\Components\MapEntry
  • Darkclow4\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.