josrom/nova-map-address
josrom/nova-map-address is a Laravel Nova field for map-based address input. It adds an interactive map to Nova resource forms, letting users search, select, and manage addresses visually so location data is easier to capture and edit in admin panels.
This package is a Laravel Nova form field that embeds Google Maps to capture an address (and typically latitude/longitude) instead of a plain text input. Docs are thin and the package has ~3 stars / 0 dependents, so treat the source as the source of truth.
Minimal path to first render:
nova constraint in composer.json before committing):
composer require josrom/nova-map-address
php artisan vendor:publish --provider="Josrom\NovaMapAddress\FieldServiceProvider"
config/services.php or a package config. Set it in .env and clear config:
GOOGLE_MAPS_API_KEY=your-key
Your key needs Maps JavaScript API and Places API (plus Geocoding API if the package reverse-geocodes on save) enabled in Google Cloud.latitude / longitude (plus a place ID in some versions). Add them if missing:
$table->string('address')->nullable();
$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();
use Josrom\NovaMapAddress\NovaMapAddress;
public function fields(Request $request)
{
return [
NovaMapAddress::make('Address'),
];
}
Where to look first (in this order):
src/ for the Field subclass — the public API (make, withMeta, any ->coordinates() style helpers) is defined there.resources/js/ for the Vue component — this tells you exactly which model attributes are read/written and what Google APIs are called.config/ for the key name and default map options (zoom, center, country restrictions).First use case: a single-address resource (e.g. a Store or Venue) where an admin drops a pin and the street address, lat, and lng persist together. That's the 20% case this package exists for.
Pair it with read-only coordinate fields. The map writes lat/lng as a side effect; expose them for trust/debugging but don't let users hand-edit them into an inconsistent state:
NovaMapAddress::make('Address')
->rules('required', 'string', 'max:255'),
Number::make('Latitude')->readonly()->hideFromIndex(),
Number::make('Longitude')->readonly()->hideFromIndex(),
Note the readonly() fields must still be present in the field set for the JS payload to round-trip in some versions — verify in the component before trusting hidden-only.
Normalize on save with a model observer. Google returns formatted addresses that vary run to run; store a canonical form and geocode defensively:
// app/Observers/StoreObserver.php
public function saving(Store $store): void
{
if ($store->isDirty('address') && $store->latitude === null) {
// fallback geocode when the map widget wasn't used (imports, API writes)
}
}
Use it on one "location" field, not five. A common mistake is dropping the map on every resource. Wrap it once and reuse:
// app/Nova/Fields/AddressField.php
class AddressField extends NovaMapAddress
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute ?? 'address', $resolveCallback);
$this->withMeta(['defaultZoom' => 12, 'country' => 'gb']);
}
}
Push config via meta() rather than editing the published vendor config. When you need per-field behavior (center point, country bias, zoom), ->withMeta([...]) keeps you upgradable and avoids a config fork the maintainer won't support.
Integration with search/filters. Because the field stores discrete latitude/longitude, don't ship a map widget into Nova's index. Compute distance in a custom filter or lens using a Haversine scope on the model, and keep the map strictly a form concern.
Testing. The Google SDK is browser-side, so PHPUnit won't exercise it. Assert the field resolves correctly and that your observer/geocoding fallback works:
it('persists coordinates from a payload', function () {
$store = Store::factory()->create([
'address' => '10 Downing St, London',
'latitude' => 51.5034,
'longitude' => -0.1276,
]);
expect($store->fresh()->latitude)->toEqual(51.5034);
});
For feature tests via Nova's Nova::actingAs, send the coordinates explicitly — Dusk/browser tests are the only place the widget itself can be validated, and even then only with a live key.
Google Maps JavaScript API error: ....[Vue warn]: Unknown custom element, run php artisan nova:publish and hard-refresh; stale public/vendor/ assets are the #1 "it's broken" cause.components=country:xx restriction and a session token in the component if you're tuning costs. In CI and local dev, mock or stub the key so test suites don't hammer the API.address_line_1 but the field defaults to address, saves look successful but persist null. Confirm the attribute in src/ before wiring the migration.composer.json for laravel/nova constraints; if it targets Nova 3, expect the Vue 2 component to fail under Nova 4/5's Vue 3 runtime. Forking the JS is a realistic outcome.decimal(10,7) (not float) for lat/lng — MySQL float rounding will make pins drift by meters and break exact-match comparisons in filters.meta keys it honors. When behavior surprises you, grep resources/js before opening an issue.withMeta() for per-field config, overriding the field class to hard-code defaults, and your model's saving/saved events for geocoding fallbacks (imports, API writes, seeders). There is no event system in the package itself — don't expect hooks.composer.json, and if it becomes load-bearing, vendor the field into app/Nova/Fields so a future Laravel/Nova bump doesn't strand you.How can I help you explore Laravel packages today?