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 Marker Field Laravel Package

genealabs/nova-map-marker-field

A Laravel Nova field that lets you pick a location by dragging a marker on an interactive map. Stores latitude and longitude coordinates on your model, making map-based location selection simple within Nova resources.

View on GitHub
Deep Wiki
Context7

Getting Started

Qualify the stack before you type anything. This field is a Nova 3-era artifact (last release 2022-06-17, archived). Nova 4's Vue 3 migration broke most field packages from that generation, so your first command is a compatibility check, not an install:

# In the app that will consume it
php artisan nova:version   # or check composer.lock for laravel/nova
composer show laravel/nova | grep versions

If you're on Nova 4/5, stop and price the native rebuild (one field class + one Vue component + two decimal columns, 2–5 dev days) before spending an afternoon fighting the asset pipeline. If you're on Nova 3.x, proceed.

Install and publish.

composer require genealabs/nova-map-marker-field:0.4.*
php artisan vendor:publish   # pick the GeneaLabs entry when prompted

Pin the constraint explicitly (0.4.*), never a floating * — there will be no future patch release.

Find the API surface by reading source, not docs. Documentation is thin and the README predates the archive. The whole package is small; read these in order:

  1. composer.json — Nova/PHP constraints, and whether dist/ ships precompiled JS.
  2. src/ — the field class. This is the API: every configurable option (center, zoom, default marker position, whatever else exists) is a fluent setter here. Read it once and you know the package.
  3. config/ — the published config file, which tells you exactly which env key feeds the Google Maps API key.
  4. dist/js/field.js — confirms assets are committed and resolvable (or that you need a legacy Node toolchain).

Wire up the key first. A missing/unauthorized Google Maps key doesn't throw — it renders a blank grey box. Get a key with the Maps JavaScript API enabled and billing attached before you debug the field.

GOOGLE_MAPS_API_KEY=...

First use case: a throwaway resource, not your real one. Create a scratch Nova resource with a nullable location pair and prove the round-trip — create, save, reload the form, view the detail page — before touching a production resource:

// database/migrations/..._add_coordinates_to_sites_table.php
$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();
// app/Nova/Site.php
public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name')->rules('required', 'max:255'),

        MapMarker::make('Location', 'latitude')   // ← verify the exact
            ->center(51.5074, -0.1278)            //    class name/signature
            ->zoom(12)                            //    against src/ first
            ->hideFromIndex(),
    ];
}

Once the round-trip is clean on the throwaway resource, copy the pattern.

Implementation Patterns

Commit to a storage contract on day one. The description says "latitude and longitude values," the implementation notes suggest a single JSON {lat, lng} payload. These have very different downstream costs. Resolve it to a written answer before authoring a migration:

  • Discrete decimal(10,7) / decimal(11,8) columns — sortable in Nova, indexable, filterable, cheap path to PostGIS later. Default to this.
  • Single JSON column — simpler field wiring, but you cannot ORDER BY or index it sanely, and every reporting query starts with JSON extraction.

If the field only writes one and you want the other, bridge it in the model with a mutator or resolveUsing/fillUsing on the field rather than living with the wrong shape forever.

Use discrete coordinates for index columns, the map for forms. Rendering a Google Maps instance per table row is a direct quota burn and a slow index page. Show text, edit with the map:

MapMarker::make('Location')->hideFromIndex(),

// Index-friendly read-only display
Text::make('Coordinates', function () {
    return $this->latitude && $this->longitude
        ? "{$this->latitude}, {$this->longitude}"
        : '—';
})->onlyOnIndex(),

Treat detail view as a viewer, not an editor. Showing the marker on detail is fine and cheap (one map). Just handle the null case — the 0.4.4 patch was specifically "detection if location is not set in detail view," so legacy nulls on already-populated tables are a known edge. Guard with ->hideFromDetail() if a blank map confuses ops.

Pair it with manual lat/lng inputs. Accessibility, JS failures, CSP-restricted environments, and bulk imports all want a numeric fallback. Two collapsible Number fields beside the map cost nothing and save an ops ticket every month.

Validate at the model or form-request layer, not on the field. The map component writes through its own mechanism; ->rules() on the field may not see the values you expect. Put nullable|numeric|between:-90,90 / between:-180,180 on the underlying attributes.

Round-trip test as a regression harness. Because the failure mode is silent, encode it:

public function test_marker_coordinates_round_trip()
{
    $this->actingAs($admin)
        ->putJson("/nova-api/sites/{$site->id}", [
            'name' => 'Depot',
            'latitude' => 51.5074,
            'longitude' => -0.1278,
        ])
        ->assertOk();

    $this->assertSame('51.5074000', $site->fresh()->latitude);
}

Add a second assertion on the HTML/asset path (/nova-vendor/.../field.js returns 200) — that's the check that catches a Nova upgrade breaking the field before an operator stares at a grey rectangle.

Normalize longitude-first confusion at the boundary. GeoJSON, PostGIS (ST_MakePoint(lng, lat)), and most tiling tools are longitude-first. The field is latitude-first by name. Write the swap down in a comment on the migration and in the model accessor; this is the single most common geo bug in back-office code.

Roll out as a template. Get one resource right, then copy the field definition + migration stub + test into a shared concern so the second, third, and tenth geo-tagged resources are a copy-paste rather than a re-decision.

Gotchas and Tips

A blank map is a key problem ~95% of the time. No exception, no console error, just grey. Check in this order: key present in the built config (php artisan config:clear after any .env edit), Maps JavaScript API enabled on the right project, billing active, HTTP referrer restrictions don't exclude your admin domain, and CSP script-src/img-src/connect-src allow maps.googleapis.com and the tile hosts.

Archived means you own it. Pin the version in composer.json, and if you make even one patch, move the fork to a private VCS path repository rather than editing vendor/ (which vanishes on the next composer install). Name an owner and a review date in the ticket — an unowned fork is worse than no adoption.

Nova 4/5: expect the asset not to load. Nova's asset pipeline and Vue runtime changed. Symptoms are a field that renders nothing, or a Vue error about unknown components. Confirm whether dist/js/field.js is committed (if not, you need a legacy Vue 2/Node toolchain you almost certainly don't want to maintain). At that point the native rebuild is cheaper than resurrecting the build chain.

Precision and types. Google returns doubles. decimal(10,7) gives ~1 cm at the equator; decimal(11,8) more. Don't use float — rounding drift on save/reload makes markers visibly jump, and it destroys WHERE lat = ? comparisons. Also decide explicitly what "unset" looks like: nullable columns, not 0,0 (which is in the Gulf of Guinea and shows up as a real place on a map).

Watch the quota math. Each form render loads the Maps JS API. One map per resource form is fine; a map per index row is not. If a stakeholder asks for "a map column on the index," push back with numbers — that's the request that turns a free tier into a monthly invoice.

Dirty-state and save semantics. Click-to-place and marker-drag must both mark the Nova form dirty, or users will place a pin, hit save, and get nothing. Test both interactions — and test saving a form without touching the map, which is where null-handling bugs hide.

Escape anything you put in a marker label. Address strings, customer names, and notes flow into Google's InfoWindow HTML. If the field lets you set a label, treat it as untrusted output.

Coordinates are often PII. Customer home addresses, employee check-ins, property records. Before the field goes live: decide who can viewAny/update the resource, whether lat/lng appears in CSV exports and Nova lenses, and whether the change joins your audit trail. Retrofitting this after ops has been pasting home addresses for a quarter is expensive.

Know your extension points before you need them. The realistic escapes, in ascending cost: (1) swap the map provider by replacing the Vue component with a Leaflet/Mapbox one — no API key, no billing, no tile-terms review; (2) fork and add the fluent setters you're missing; (3) rebuild natively. Because the surface is one component, option (1) is often cheaper than getting Google billing approved.

Don't pull it in transitively. It should be a direct dependency on exactly the apps that need it, so the inventory question ("which apps depend on the archived package?") is answerable with composer why.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager