parabol/parabol-google-maps-picker-bundle
A Symfony bundle that adds an interactive Google Maps location picker form type, letting users search for an address or drop a pin and submit precise latitude/longitude coordinates. Configurable map options, simple asset setup, and easy integration into existing forms.
Minimal steps to bring this into a Laravel project:
composer require parabol/parabol-google-maps-picker-bundle
Despite the -bundle suffix (a Symfony naming convention), this package exposes a form field type that renders a Google Maps picker widget and captures latitude / longitude into your model. It is not a Laravel package with an auto-discovered ServiceProvider in the usual sense — check the repository README for the current install path before assuming php artisan vendor:publish will do anything.
First things to look at, in order:
README.md on the GitHub repo — it is the only documentation. Look for the form type class name (typically something like Parabol\GoogleMapsPickerBundle\Form\Type\GoogleMapsPickerType) and the required options.Resources/config/ in the package — this is where the JS asset, default options, and any routing for the geocode endpoint live.First use case: a Venue or Location model with latitude and longitude decimal columns.
// app/Models/Venue.php
protected $fillable = ['name', 'address', 'latitude', 'longitude'];
protected $casts = ['latitude' => 'float', 'longitude' => 'float'];
Then wire the picker into a form (adapting the Symfony FormType to whatever Laravel form layer you use — Livewire, Filament, or plain Blade). In Blade the core contract is: a text input for the address, two hidden inputs for latitude/longitude, and the package JS that listens for a map click or autocomplete selection and writes those values.
Two-coordinate entity pattern. The cleanest fit is a model with nullable latitude/longitude decimals. Render the picker, let the user pick a point, submit, and persist. On re-edit, hydrate the map from the stored coordinates.
<div
x-data="mapsPicker({
lat: {{ $venue->latitude ?? 'null' }},
lng: {{ $venue->longitude ?? 'null' }},
key: '{{ config('services.google_maps.key') }}'
})"
>
<input type="text" x-model="address" placeholder="Search a place">
<div x-ref="map" style="height: 400px"></div>
<input type="hidden" name="latitude" x-model="lat">
<input type="hidden" name="longitude" x-model="lng">
</div>
Validation gate. Never trust the widget. Require coordinates together and bound them:
$request->validate([
'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'],
'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'],
]);
Persist, then optionally reverse-geocode to fill a human-readable address if the user didn't type one.
Livewire integration. Because the widget is JS-driven, the common trap is state desync. Bind the hidden inputs to Livewire properties and dispatch an event when the map marker moves:
protected $listeners = ['coordinatesChanged' => 'updateCoordinates'];
public function updateCoordinates($lat, $lng)
{
$this->latitude = round((float) $lat, 7);
$this->longitude = round((float) $lng, 7);
}
map.addListener('click', e => {
Livewire.dispatch('coordinatesChanged', {
lat: e.latLng.lat(),
lng: e.latLng.lng(),
});
});
Filament field. Wrap the same contract in a custom Field, storing latitude/longitude as two sibling form fields via ->dehydrateStateUsing(). Do not try to cram both into one column — sorting and distance queries in MySQL/Postgres need them separate.
Distance queries. Once you have the columns, add a scope using ST_Distance_Sphere (MySQL) or earth_distance (Postgres). Coordinates stored as floats at 7 decimal places are fine for street-level accuracy.
ServiceProvider. You will likely need to register the classes manually or use the package only for its JS asset and copy the widget logic into your own Blade component. Treat the package as a template more than a dependency.resources/js and pinning it, rather than depending on a composer update bringing unreviewed changes.maps.googleapis.com/maps/api/js must be loaded before the picker initializes. In a Livewire or SPA context, dynamically inserted DOM may run the init before the script resolves. Use the official callback= parameter or the loader utility rather than document.ready.center and zoom, the map renders grey. Default to a sensible city center when no coordinates are stored.nullable plus required_with is the pattern; do not use bare required on both, or a user picking only a point with no reverse-geocode will fail validation.decimal(10,7) for latitude, decimal(11,7) for longitude covers the full globe. float is acceptable but avoid varchar.latitude/longitude validation rejects out-of-range values and that null coordinates are handled gracefully on the edit form.composer.json before using in production; unknown licensing is a real blocker for client work.How can I help you explore Laravel packages today?