Installation:
composer require webbingbrasil/filament-maps
Publish assets (if needed):
php artisan vendor:publish --provider="Webbingbrasil\FilamentMaps\FilamentMapsServiceProvider"
First Widget:
Create a widget extending MapWidget in app/Filament/Widgets/Map.php:
use Webbingbrasil\FilamentMaps\Widgets\MapWidget;
class Map extends MapWidget {
protected int $columnSpan = 'full';
public function getMarkers(): array { return []; }
}
Register it in app/Providers/Filament/AdminPanelProvider.php:
public static function panel(Panel $panel): Panel {
return $panel->widgets([
\App\Filament\Widgets\Map::class,
]);
}
First Use Case: Display a map with a single marker:
public function getMarkers(): array {
return [
Marker::make('Office')->lat(40.7128)->lng(-74.0060)->popup('New York HQ'),
];
}
Marker Management:
User::all()) and map to Marker objects:
public function getMarkers(): array {
return User::query()
->get()
->map(fn ($user) => Marker::make($user->name)
->lat($user->latitude)
->lng($user->longitude)
->popup("User: {$user->email}")
);
}
MarkerClusterGroup:
use Webbingbrasil\FilamentMaps\MarkerClusterGroup;
public function getMarkers(): array {
return [
MarkerClusterGroup::make()->markers([
Marker::make('A')->lat(1)->lng(1),
Marker::make('B')->lat(2)->lng(2),
]),
];
}
Layer Integration:
public function getLayers(): array {
return [
\Webbingbrasil\FilamentMaps\Layers\OpenStreetMap::make(),
\Webbingbrasil\FilamentMaps\Layers\DarkModeTile::make(),
];
}
Action Integration:
public function getActions(): array {
return [
Actions\ZoomAction::make()->label('Zoom In'),
Actions\CenterMapAction::make()->label('Center Here'),
\Filament\Actions\Action::make('Refresh')
->action(fn () => $this->refreshMarkers())
->icon('heroicon-o-arrow-path'),
];
}
Event Handling:
moveend) via JavaScript:
protected string $extraJs = '
map.on("moveend", function() {
console.log("Map moved to:", map.getCenter());
});
';
Geocoding:
Use the Geocoder helper to convert addresses to coordinates:
use Webbingbrasil\FilamentMaps\Support\Geocoder;
$coordinates = Geocoder::geocode('1600 Amphitheatre Parkway, Mountain View');
Marker::make('Google HQ')->lat($coordinates['lat'])->lng($coordinates['lng']);
Custom Controls:
Extend MapWidget to add custom Leaflet controls:
class CustomMap extends MapWidget {
protected string $extraJs = '
L.control.scale().addTo(map);
';
}
Performance: For large datasets, implement lazy-loading:
public function getMarkers(): array {
return Marker::make('Cluster')->lat(0)->lng(0)
->clusterOptions(['spiderfyOnMaxZoom': true])
->popup('Click to load details');
}
Leaflet Version Conflicts:
spatie/laravel-geocoder). Use npm for custom builds if needed.resources/js/app.js for duplicate Leaflet includes.Marker Overlapping:
MarkerClusterGroup with custom options:
MarkerClusterGroup::make()->options([
'spiderfyOnMaxZoom': true,
'maxClusterRadius': 80,
]);
Dark Mode Issues:
protected string $extraCss = '
.leaflet-dark-mode .leaflet-tile {
filter: brightness(0.8) !important;
}
';
Action Button Styling:
Actions\CenterMapAction::make()->icon('heroicon-o-map-pin')->extraClasses('text-blue-500');
Geolocation Errors:
extraJs:
protected string $extraJs = '
map.locate({ setView: true, maxZoom: 16 });
map.on("locationerror", function(e) {
alert(e.message);
});
';
Console Logs:
Add debug logs via extraJs:
protected string $extraJs = '
map.on("click", function(e) {
console.log("Clicked at:", e.latlng);
});
';
Inspect Elements:
Use browser dev tools to inspect Leaflet containers (e.g., .leaflet-container). Common issues:
position: relative on parent containers.Clear Cache: After updates, run:
php artisan filament:cache-reset
npm run dev
Custom Layers:
Extend Webbingbrasil\FilamentMaps\Layers\Layer to create reusable layers:
class CustomLayer extends Layer {
public function getUrl(): string {
return 'https://custom-tile-server/{z}/{x}/{y}.png';
}
public function getOptions(): array {
return ['attribution': 'Custom'];
}
}
Marker Icons: Replace default icons with custom SVG/URLs:
Marker::make('Custom')->icon('https://example.com/custom-icon.png');
Polygons/Circles:
Use Leaflet’s L.polygon/L.circle via extraJs:
protected string $extraJs = '
L.circle([51.505, -0.09], 500, { color: "red" }).addTo(map);
';
Server-Side Rendering:
For SSR compatibility, disable Leaflet’s interactive: true in extraJs:
protected string $extraJs = '
map.options.interactive = false;
';
How can I help you explore Laravel packages today?