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

Nova Map Card Laravel Package

imumz/nova-map-card

imumz/nova-map-card is a Laravel Nova card for adding an interactive map to your dashboard or resource pages. Plot locations with markers and customize map options to give geographic context at a glance.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Nova card, not a field — it renders in the dashboard grid or in a resource detail panel, not inside forms. It wraps vue2-leaflet, so it is a Nova 3 (Vue 2) package only (see Gotchas). Verify that before anything else:

composer show imumz/nova-map-card          # check laravel/nova constraint in package composer.json

Install and publish assets:

composer require imumz/nova-map-card
php artisan nova:publish

Where to look first:

  1. src/ — the card class (commonly NovaMapCard / MapCard); note its component() string and any withMeta() defaults. This tells you the exact meta keys the Vue side expects.
  2. resources/js/components/ — the Vue component; this is the source of truth for which props (center, zoom, markers, tileUrl, attribution, height) are actually read.
  3. composer.json laravel/nova constraint — confirm it is ^2.0|^3.0 and not ^4.0.

Register in app/Providers/NovaServiceProvider.php (or a dashboard's cards()):

public function cards(): array
{
    return [
        (new NovaMapCard())->width('full'),
    ];
}

First use case: a full-width dashboard card pinning every store / customer / job site to a map, with the card's PHP side building a markers array and passing it through the card's meta. Confirm the exact setter names against src/ — if there are no fluent setters, use the generic escape hatch:

(new NovaMapCard())->withMeta([
    'center' => [-1.286389, 36.817223],
    'zoom'   => 12,
    'markers' => [
        ['lat' => -1.286389, 'lng' => 36.817223, 'title' => 'HQ'],
    ],
]);

Implementation Patterns

Resource-scoped map (the common case). Nova instantiates the resource bound to the model, so $this->resource is available inside cards():

public function cards(Request $request): array
{
    $markers = $this->resource->sites->map(fn ($s) => [
        'lat'   => (float) $s->latitude,
        'lng'   => (float) $s->longitude,
        'title' => $s->name,
    ])->all();

    return [
        (new NovaMapCard())->withMeta([
            'center'  => [(float) $this->resource->latitude, (float) $this->resource->longitude],
            'zoom'    => 14,
            'markers' => $markers,
        ])->width('full'),
    ];
}

Dashboard aggregation with request filtering. Build a thin subclass so the query logic lives in one place rather than in NovaServiceProvider:

class SiteMapCard extends NovaMapCard
{
    public function __construct(protected ?string $region = null) {}

    public function component() { return 'nova-map-card'; }

    public function withMeta(): array
    {
        $sites = Site::query()
            ->when($this->region, fn ($q) => $q->where('region', $this->region))
            ->get();

        return array_merge(parent::withMeta() ?? [], [
            'center'  => [$sites->avg('latitude'), $sites->avg('longitude')],
            'zoom'    => 6,
            'markers' => $sites->map->only(['id', 'name'])->toArray(),
        ]);
    }
}

Register it once per region and you get several dashboards for free.

Always cast to float. Marker arrays coming out of an Eloquent pluck/get contain strings; some Leaflet builds silently drop markers whose lat/lng aren't numeric. (float) on the boundary saves an hour of debugging.

Tile provider swap. For anything beyond a demo, point at a provider with an explicit attribution (Mapbox, Stadia, MapTiler) rather than OSM's public tiles, and keep the API key server-side in a Nova card meta value rather than hard-coded in the Vue component.

Pair with a detail field. A map card answers "where", a Text/Number field answers "which one". On detail views, stack the card above your fields so the marker click gives context to the record below.

Clustering. If you have more than ~50 markers, plan for vue2-leaflet-markercluster — the card itself rarely ships clustering, so you will fork the Vue component, add the plugin to resources/js/components, and register the layer alongside the marker loop.

Gotchas and Tips

Nova 4/5 will not work. Nova 4 moved to Vue 3 + Inertia; vue2-leaflet is Vue 2 only. On Nova 4+ the card either fails to register its component or renders an empty box. If you are on Nova 4 or 5, fork the Vue component to @vue-leaflet/vue-leaflet, or pick a maintained alternative — do not spend time debugging the 2020 build.

The map has zero height. Leaflet requires an explicit container height. If you get a grey/blank strip, either the card's default height is unset or your width('full') grid row collapsed. Set ->height('500px') (or the meta equivalent) and confirm .leaflet-container { height: 100% } survives Nova's CSS.

Missing Leaflet CSS. Marker tiles render as a jumble of 256px squares. The card's Nova::style() registration in the service provider only runs after nova:publish; if you skipped it or ran nova:publish before upgrading, republish.

Broken marker icons (404s). The classic Leaflet + bundler problem: L.Icon.Default resolves marker-icon.png relative to the compiled asset path, which Nova's mix doesn't serve. Fix by setting the icon paths explicitly in the Vue component:

import icon from 'leaflet/dist/images/marker-icon.png';
import icon2x from 'leaflet/dist/images/marker-icon-2x.png';
import shadow from 'leaflet/dist/images/marker-shadow.png';
L.Icon.Default.mergeOptions({ iconUrl: icon, iconRetinaUrl: icon2x, shadowUrl: shadow });

Meta is read at mount, not reactive. Changing markers/center after the component is created will not re-render the map. If you need updates, force a fresh card (e.g. key on a query string), or emit into the Leaflet instance directly instead of rewriting withMeta.

Asset staleness. Cards ship pre-built JS under dist/. After git pull or a version bump, run php artisan nova:publish — otherwise you are running the old bundle and wondering why your fork changed nothing.

Debugging order. (1) Browser console for Unknown custom element / 404s — tells you assets vs. component registration; (2) Network tab for missing leaflet.css and marker PNGs; (3) php artisan route:list | grep nova to confirm the card isn't 403-ing on a policy; (4) only then read the Vue component. Because the package is 5-star and last released 2020-12-14, expect to read resources/js/components/ rather than the README whenever behaviour surprises you.

Extension point. The Vue component is the only meaningful extension surface — publish it, edit it in your app's resources/js, and rebuild with npm run dev in your Nova asset pipeline (or drop a pre-built bundle into dist/). Do not patch inside vendor/; the next composer update erases it.

Attribution is not optional. OSM's tile usage policy rejects apps without visible attribution and rate-limits heavy dashboard polling. If several users open a map dashboard at once, switch providers before it becomes an incident.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
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