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

Leaflet Map Laravel Package

imumz/leaflet-map

Laravel package for adding interactive Leaflet maps to your views. Render maps with markers, polygons, and GeoJSON, configure tiles and controls, and pass data from PHP without writing JavaScript. Lightweight integration for location-aware apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the Nova constraint before anything else. This package last shipped in January 2021, so its composer.json almost certainly pins laravel/nova to ^3.0. Read that file first:

composer require imumz/leaflet-map
cat vendor/imumz/leaflet-map/composer.json   # check "laravel/nova", "illuminate/*", "php"

If you are on Nova 4 or 5, stop here and treat the package as source material for a local field rather than a dependency (see Gotchas).

2. Look at these three places, in order:

  • vendor/imumz/leaflet-map/src/ — the field class: which make()/fluent methods actually exist (latitude(), longitude(), zoom(), center(), etc.), the default attribute names, and whether it defines fill()/resolve().
  • vendor/imumz/leaflet-map/resources/js/ — the Vue component and the Leaflet bootstrap (CDN vs bundled, tile URL, marker handling).
  • vendor/imumz/leaflet-map/dist/js/field.js — the compiled asset that Nova actually loads. This is what you must patch if you fork without a JS build step.

3. Migrate two coordinate columns, not one, since map fields of this vintage write two attributes:

Schema::table('locations', function (Blueprint $table) {
    $table->decimal('latitude', 10, 7)->nullable();
    $table->decimal('longitude', 10, 7)->nullable();
});

4. First use case — drop it into a resource form and detail view:

use Imumz\LeafletMap\LeafletMap; // verify namespace against vendor/imumz/leaflet-map/src

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name'),

        LeafletMap::make('Location')
            ->latitude('latitude')
            ->longitude('longitude')
            ->zoom(13)
            ->center(51.505, -0.090)   // confirm method names in the field class
            ->hideFromIndex(),         // maps in index cells render poorly
    ];
}

Open the form, click the map to drop a marker, save, then re-open and confirm both columns persisted. That single round trip is your smoke test — verify it before building anything else on top.

Implementation Patterns

Separate form and detail declarations. A map that accepts clicks in a form is usually a liability on detail; declare twice so the detail instance can be read-only and sized differently:

LeafletMap::make('Location')->onlyOnForm(...),
LeafletMap::make('Location')->onlyOnDetail()->zoom(15),

Index listings. There is no useful index rendering here — Leaflet instances per row are heavy and the compiled asset may not survive Nova's index layout. Use a plain text column instead:

Text::make('Coordinates', fn ($r) => $r->latitude && $r->longitude
        ? "{$r->latitude}, {$r->longitude}"
        : '—')
    ->onlyOnIndex(),

Single JSON/point column. If you prefer one column, keep the field's two attribute names but bridge them on the model:

protected $appends = ['latitude', 'longitude'];

public function getLatitudeAttribute() { return $this->location['lat'] ?? null; }
public function setLatitudeAttribute($v) { $this->attributes['location'] = json_encode([...]); }
protected $guarded = []; // or make the bridge fillable-aware

Test the save path explicitly — mutators plus Nova's fill() interact in ways that are easy to get wrong, especially when only one of the pair is submitted.

Geocoding workflow. The common day-to-day need is "type an address, get coordinates." This package is a picker, not a geocoder. Pair it with:

  • a Text field + Nova action (or saving observer) that calls Nominatim/Mapbox and writes latitude/longitude before the field hydrates, or
  • a Place-style search field if you adopt a maintained alternative.

Beware ordering: Nova hydrates fields from the model, so a geocode action must run before the form submits, not after. An action on the detail screen ("Geocode address") is simpler and far easier to debug.

Fallback center for new records. Set a sensible default so the map isn't centered on 0,0 (ocean off Africa, a classic support ticket):

LeafletMap::make('Location')
    ->center(config('maps.default_lat'), config('maps.default_lng'))
    ->zoom(config('maps.default_zoom', 11)),

Put these in config/maps.php so ops can change tiles/default city without a deploy.

Validation. The field does not guarantee sane coordinates. Add rules on the resource or a FormRequest:

'latitude'  => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'],
'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'],

Reuse across resources. Wrap in a small helper/trait returning a configured instance, so tile URL, zoom, and attribute names are defined once. This matters when you eventually replace the package — one swap point instead of twenty resources.

Gotchas and Tips

Nova 4/5 will not work out of the box. Nova 4 moved field registration and Vue version; a Nova 3-era field.js fails with [Vue warn]: Failed to mount component or a blank field panel. Confirm before planning: read vendor/imumz/leaflet-map/composer.json and the Nova.booting block in resources/js/field.js. If incompatible, the fastest sane path is to scaffold a local field with php artisan nova:field acme/leaflet-map and copy the Vue component + tile logic across. With 8 stars, 0 dependents, and no release since Jan 2021, assume no upstream fix is coming.

Assets are pre-built. dist/js/field.js is checked in. Any behavioral change (default zoom, tile provider, marker icon path) requires either editing dist/ by hand or npm install && npm run prod inside the package — and the Mix/Vite config from 2021 may not build on a current Node. Forking to a private VCS/path repo is usually cheaper than fighting the toolchain:

"repositories": [{ "type": "path", "url": "../nova-leaflet-map" }],
"require": { "imumz/leaflet-map": "dev-main" }

Tiles and attribution. If the map is grey with a broken-image marker, it's a tile or asset-path problem, not a code problem. Check the browser network tab, then: OpenStreetMap tile URLs require attribution and a valid Referer; heavy admin usage behind a proxy often gets 403/429 from OSM. Use a keyed provider (Mapbox, Stadia, Thunderforest) or self-hosted tiles for anything beyond a prototype, and add the attribution string. Also check your CSP — img-src must allow the tile host or the map silently renders empty.

0 overwriting real coordinates. Map widgets frequently emit 0/'' when a user pans without clicking. Guard with ->nullable() and a fillUsing that ignores empty input, or you will quietly move assets to the Gulf of Guinea.

Decimal precision. Use decimal(10,7); MySQL FLOAT/DOUBLE can round-trip 51.5074123 to 51.50741, which is fine for pins but not for anything measured. Some map fields store degrees/minutes strings — normalize in a model accessor rather than in the view.

Debugging a blank field. In order: (1) php artisan package:discover ran and the provider is in bootstrap/cache/packages.php; (2) dist/js/field.js is actually served — open the URL directly; (3) console errors, not the network tab, since Vue mount failures are logged there; (4) php artisan nova:publish does not publish third-party fields — don't chase that. APP_DEBUG + nova:check first saves time.

Two maps on one form. Confirm the component keys off a unique field attribute, not a hardcoded id. If both maps fight, patch the component to use this.field.attribute in the DOM id and marker refs.

Extension points are thin by design. You get a fluent field class plus a Vue component; there is no event bus, driver system, or config file. Realistic extension options: (a) subclass the field class and override fill()/resolve()/meta(); (b) ->withMeta([...]) and read this.field in a patched component; (c) a custom Vue component entirely, treating this package's field.js as a reference implementation.

Treat it as a code snippet, not a dependency. Zero dependents and a single maintainer means no security or compatibility triage. Pin the version (or vendor the fork), and add one integration test that posts the form and asserts both columns persist — that test is your upgrade canary the day you replace this with a maintained field.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle