naif/address_autocomplete
Add address autocomplete to your Laravel forms with this package. As users type, it suggests matching addresses and can auto-fill fields like street, city, and postal code, making checkout and address entry faster and less error-prone.
Install and register the field
composer require naif/address_autocomplete
php artisan vendor:publish --provider="Naif\AddressAutocomplete\FieldServiceProvider"
The package ships a Nova field, a config file, and a Vue component. After publishing, open config/address_autocomplete.php and drop in your Google Places key — most versions read an env var such as GOOGLE_PLACES_API_KEY or NOVA_ADDRESS_AUTOCOMPLETE_KEY, so set that in .env and config/address_autocomplete.php will pick it up. Remember php artisan config:clear after editing the env.
First use case: one address field on a resource
In the Nova resource you want to enrich (e.g. app/Nova/Customer.php):
use Naif\AddressAutocomplete\AddressAutocomplete;
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Name'),
AddressAutocomplete::make('Address'),
];
}
Make sure the underlying address column exists on the model and is fillable. Add Address to $casts if you plan to store structured data (see below), then load /nova/resources/customers/1/edit and start typing — the field should attach Google Places autocomplete to the input and write the selected formatted address into the column on save.
Where to look first
config/address_autocomplete.php — the key and any default options (country/type restrictions, script inclusion).resources/js/components/Field.vue (or similarly named) inside the package — this is where the Places widget is mounted; almost every customisation ends up here or in a subclass.src/ — check which withMeta() keys are exposed as fluent setters.If the dropdown never appears, open DevTools → Network, filter for maps.googleapis.com, and confirm the script loaded and the key is being serialised into the field meta before you debug anything else.
Plain address string (simplest)
The package's default behaviour is a single Text-style column that stores the formatted address. Good for line-of-business apps where you only need "what the user selected" for display or mailing labels.
Store the full place, not just a string
Autocomplete returns a rich PlaceResult. The common pattern is to keep the formatted address in address and cast a companion JSON column:
// migration
$table->string('address')->nullable();
$table->json('address_components')->nullable();
$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();
// model
protected $casts = ['address_components' => 'array'];
Then in the field's withMeta()/component, listen to the place_changed event and write geometry.location and address_components into hidden fields. This is the highest-leverage pattern: it turns the widget from a convenience into structured data you can later geo-query or validate.
Mapping components to first-class columns
Most teams don't want a JSON blob in the DB. Use an accessor or an AddressData value object to pull street_number, route, locality, postal_code, country out of address_components on save:
protected static function booted()
{
static::saving(function ($model) {
$parts = collect($model->address_components ?? [])
->keyBy(fn ($c) => $c['types'][0]);
$model->city = $parts['locality']['long_name'] ?? null;
$model->zip = $parts['postal_code']['long_name'] ?? null;
$model->country = $parts['country']['short_name'] ?? null;
});
}
Restrict to a country or address type
Use the field's fluent options (or config defaults) so you don't get PO boxes or airport names in a shipping form:
AddressAutocomplete::make('Shipping Address')
->countries(['us', 'ca'])
->types(['address']);
Keep the restriction in the field and in validation — the widget is client-side and can be bypassed.
Pairing with validation
Don't trust the string blindly. A pragmatic rule set:
'address' => ['required', 'string', 'max:255'],
'latitude' => ['nullable', 'numeric', 'between:-90,90'],
'longitude' => ['nullable', 'numeric', 'between:-180,180'],
If you require a verified place, store a flag set when place_changed fires and reject submissions without it. This catches users who type free text and never pick a suggestion.
Reuse across resources
Wrap the field in a small factory so every resource gets identical options:
class AddressField extends AddressAutocomplete
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute, $resolveCallback);
$this->countries(['us'])->types(['address']);
}
}
Then use AddressField::make('Address') everywhere — one place to change when Google's API drifts.
Detail and index views
The field is form-oriented. On detail/index, either use ->onlyOnForms() and render the stored string with a separate Text::make('Address')->exceptOnForms(), or format it as a Google Maps link:
Text::make('Address', function () {
return sprintf(
'<a href="https://maps.google.com/?q=%s,%s" target="_blank">%s</a>',
$this->latitude, $this->longitude, e($this->address)
);
})->asHtml(),
Testing around it
The widget is JS-only, so feature tests just see a text input. Assert the post-autocomplete state by posting the fields you expect (address, latitude, longitude) directly. For UI coverage use Nova Dusk and stub the network with a fixture rather than hitting Google in CI.
package.json/nova.mix.js compatibility before committing to it.AutocompleteSessionToken if the package doesn't, or at minimum set a quota alert..env. If the dropdown works locally but not on staging, it's almost always php artisan config:cache having frozen the old key. Re-cache on deploy.maps.googleapis.com must be allow-listed for script-src and connect-src, and maps.gstatic.com/fonts.gstatic.com for styles. Symptom: silent failure, one console CSP violation.place_changed handler to only write when a real place is returned.latitude/longitude, make the verified-place flag required in validation rather than assuming the widget guarantees it.RefererNotAllowedMapError in the console? (4) does the input have the autocomplete class the component expects (renamed attributes break this)? That sequence resolves ~90% of reports.component() to point at your own compiled Vue file; use withMeta() to pass extra options (map defaults, component restrictions) into the JS; and override the config's script URL if you need loading=async or a region parameter (®ion=US) for better suggestion quality.app/Nova/Fields (or a private package) so a future Nova upgrade doesn't leave you blocked on an abandoned repo — and so a Google API change is a one-file fix you control.How can I help you explore Laravel packages today?