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

Filament Locationpickr Field Laravel Package

lara-zeus/filament-locationpickr-field

A Filament form field that lets users pick a location on an interactive map. Search for an address, drop a pin, and save latitude and longitude coordinates straight to your model—simple, lightweight map integration for Laravel admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

Preconditions before composer require. This field is a thin adapter with two hard external dependencies: Filament 5 (v5.0.0's entire release note is "support filament 5" — treat it as a hard cutover, not dual support) and a billing-enabled Google Cloud project with Maps JavaScript API and Places API enabled. Read vendor/lara-zeus/filament-locationpickr-field/composer.json first; it is a faster and more honest source of PHP/Laravel/Filament floors than the README. If the panel is on Filament 4, stop here — adopting this becomes a framework migration.

composer require lara-zeus/filament-locationpickr-field:"^5.0"

Pin the constraint explicitly. With one star, zero dependents, and a single maintainer, ^5.0 floating into an untested patch is not a risk worth taking on a one-line changelog.

Provision the key before writing any PHP. Create a browser key restricted to HTTP referrers (your panel domains, including staging) and to Maps JavaScript API + Places API only. Put it in config/services.php and .env, not in a Blade template:

// config/services.php
'google_maps' => ['key' => env('GOOGLE_MAPS_BROWSER_KEY')],

Set quota caps and a budget alert in the same sitting. The org-side approval of that key is the real critical path; the coding is hours.

Publish and read the config. Publishing is where you discover the actual option names — do it before you write field code:

php artisan vendor:publish --tag="filament-locationpickr-field-config"

Then open the published config, the field class, and its Blade view. The package is small enough to read end-to-end in ten minutes, and doing so answers the questions the README won't: what column(s) it writes, whether it degrades without a key, and whether the map container is wire:ignore-wrapped.

Add the columns. Nullable decimals, seven places — roughly centimetre precision, enough for any door:

$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();

decimal(10,7) gives three integer digits and seven decimal places, which covers latitude ±90 and longitude ±180 in a single definition. Resist spatial columns for a picker; they buy nothing here and complicate every query and fixture.

First use case. One already-scoped screen — a venue, branch, or delivery-point resource — in a secondary panel. Register the field, open a real form, drag the marker, save, and confirm the row. That is the whole spike.

use LaraZeus\LocationPickr\LocationPickr; // verify exact FQCN against the installed version

LocationPickr::make('latitude')
    ->label('Location')
    // confirm the real API: some versions expect a single field name that
    // writes both coordinates, others expect a lat/lng column pair
    ;

Do not take the method names above on faith — the README at this adoption level is documentation of intent, not a contract. The field class is the source of truth.

Implementation Patterns

Wrap the vendor field immediately. One internal class, twenty lines, and the swap-to-something-else cost drops from a survey to a diff:

namespace App\Filament\Fields;

use LaraZeus\LocationPickr\LocationPickr;

class MapPicker extends LocationPickr
{
    // defaults, label, helper text, map options live here
}

Every resource references MapPicker, never the vendor namespace. When Google pricing, ToS, or the package's maintenance posture changes, you change one file.

Persist the pair plus its provenance. Coordinates alone are not recoverable — if the widget is replaced or a pin is obviously wrong, nobody can tell what it was meant to be. Store the raw provider context alongside:

$table->string('address_formatted')->nullable(); // display only, check ToS first
$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();

Read the field's save path before you decide what to persist beyond lat/lng. Google's Places caching rules restrict how long you may retain place IDs and some place data; if the field writes a place_id or formatted address into your model, that is a ToS question, not a storage convenience.

Cast to float on read, not string. Eloquent returns decimals as strings, which quietly breaks abs() comparisons, distance math, and JSON payloads:

protected function casts(): array
{
    return [
        'latitude'  => 'float',
        'longitude' => 'float',
    ];
}

If you need the string precision preserved for round-tripping, use 'decimal:7' instead and cast at the calculation boundary.

Validate the pair as a pair. The failure mode of a two-column location is half a location. A row with a latitude and no longitude is a bug that survives to production and renders a marker in the Gulf of Guinea:

->rules([
    fn (Get $get) => $get('latitude') === null
        ? ['nullable']
        : ['required', 'numeric', 'between:-90,90'],
])

Better: a Rule::requiredIf pair plus a model-level saving hook that nulls both if either is empty. Decide deliberately whether "no location" is null/null or 0/0 — it must be the former, because 0,0 is a real coordinate and will pollute every proximity query you ever write.

Keep a manual fallback visible. The field's behaviour on a missing key, exhausted quota, or blocked Maps JS is undocumented. Never let that be the only path to saving a record:

Grid::make(2)->schema([
    MapPicker::make('latitude'),
    TextInput::make('latitude_manual')->numeric()->hidden(fn () => $this->mapLoaded()),
])

Simplest robust variant: render the map field and two plain numeric inputs, and let ops paste coordinates when the map is blank. Degraded, not blocked.

Use defaults to fight re-panning. Set the map centre and zoom from config to your operating region once, in MapPicker, not per-field. A map that opens on the Atlantic every time is a small tax paid hundreds of times.

Display and navigate in tables. A TextColumn showing "51.5074, -0.1278" is enough, with a url() to https://maps.google.com/?q=lat,lng and openUrlInNewTab(). Do not embed a map in a table column; you will pay for a JS load per row.

Test the state, not the map. CI must not depend on Google. Assert form fill and persistence with Livewire test helpers — fillForm(['latitude' => 51.5, 'longitude' => -0.12]), assertFormSet, assertHasNoFormErrors, then assert the model columns. Browser-level smoke for the actual map is a nightly/manual job at best; a headless run against Google in CI is a quota leak and a flake source.

Do the geocoding server-side, in a job. If you need a normalised postal address, the field doesn't give you one. Dispatch a job after save that calls a geocoding service, writes the formatted address back, and caches the response — never call Places from a queued worker per keystroke, and never call it on every table render.

Secondary panel first, resource smoke test before and after. Plugin breakage in Filament is render-time, not boot-time. A test that boots the panel, hits the resource form route, and asserts 200 catches the lagging-plugin class of failure that php artisan about will not.

Gotchas and Tips

  • "Support filament 5" is the entire changelog for a major. Pin it, upgrade Filament core plus every plugin in the panel as one reviewed set, and keep a composer.lock revert documented. The usual failure is a Filament 5 app with one straggler plugin, and this is the straggler.
  • Debug map failures in the browser console, not the server log. RefererNotAllowedMapError means the referrer restriction doesn't cover that hostname (very often staging, or localhost with a port). A greyed map with a "development purposes only" watermark means billing isn't enabled on the project. Blank container with no console error usually means the assets didn't load or the container lost its Alpine init.
  • Use one key per environment. A single key shared across local, staging, and production forces you to whitelist localhost in a production key and makes cost attribution impossible.
  • Quota exhaustion is silent to the user. There is no documented graceful-degradation contract. Confirm empirically what happens with a deliberately bad key in staging, and keep the manual numeric inputs shipped so the answer doesn't matter.
  • wire:ignore matters. Leaflet-family widgets break when Livewire re-renders their container. If the picker lives inside a repeater, a modal, or a form with ->live() fields, check the Blade view for wire:ignore and test the interaction order: open modal → set another field → touch the map. This is the single most likely bug you'll file.
  • One coordinate pair per record. A marker field is not a geometry field. Geofences, routes, multi-point delivery areas, and service polygons are out of scope — do not stretch it.
  • Accessibility is genuinely unresolved. Drag-a-marker is not keyboard-operable and map tiles may not meet regional/localisation requirements. The numeric fallback isn't just a resilience trick; it's the accessibility story.
  • The vendor is the extension point. Config publish and view override are the supported customisation surfaces; beyond that, fork. MIT plus a small codebase makes that a realistic fallback rather than an aspiration — verify it before you're forced to.
  • Budget scales with usage, not seats. A dozen ops users is noise; a large team doing high-frequency address search is a recurring line item that a budget owner reading "MIT-licensed" will assume is free. Instrument it on day one and put the spend owner's name in the runbook.
  • Write the rollback down. composer.lock revert, plus any republished config or views, plus a note that the database columns are plain decimals and survive the package's removal untouched. That last part is the payoff of refusing spatial types.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console