HTML5 browser geolocation package for Laravel with Livewire 4 support, GPS capture, reverse geocoding, maps, and API integration.
mayaram/laravel-browser-location is a Laravel package for html5 browser geolocation package for laravel with livewire 4 support, gps capture, reverse geocoding, maps, and api integration..
It currently has 10 GitHub stars and 162 downloads on Packagist (latest version v2.1.3).
Install it with composer require mayaram/laravel-browser-location.
Discover more Laravel packages by mayaram
or browse all Laravel packages to compare alternatives.
Last updated
Capture browser-based GPS location in Laravel using the HTML5 Geolocation API.
Works with Laravel 10-13, plain Blade, and Livewire 3 / 4.
<x-browser-location-tracker />)excellent, good, poor, unknown)JS → API → Laravel)browser_locations tablephp artisan browser-location:installbrowser-location.validate)composer require mayaram/laravel-browser-location
Run the installer command:
php artisan browser-location:install
This publishes config / views / migrations and runs migrations automatically.
Drop the tracker anywhere in any Blade view — including inside a Livewire component:
<x-browser-location-tracker />
Default behaviour:
auto-capture="true"andforce-permission="true"are on by default, so the browser will request the user's location immediately. Set them tofalseif you want manual control.
| Prop | Type | Default | Description |
| -------------------------- | ------ | ------------------------------- | ----------------------------------------------------------------------------------- |
| button-text | string | 'Share GPS location' | Label for the (hidden by default) trigger button. |
| auto-capture | bool | true | Requests location on page load and after every Livewire navigation. |
| force-permission | bool | true | Shows a full-screen overlay until the user grants permission. |
| watch | bool | false | Continuously tracks position using watchPosition(). |
| livewire-method | string | 'setBrowserLocation' | Livewire component method called on successful capture. Leave empty in plain Blade. |
| required-accuracy-meters | float | 200 | Threshold for the is_accurate flag in the payload. |
| enable-high-accuracy | bool | true | Requests the most accurate reading from the device GPS. |
| timeout | int | 12000 | Milliseconds before the location request times out. |
| maximum-age | int | 0 | Milliseconds a cached location is considered fresh (0 = always fresh). |
| auto-save | bool | false | Automatically POSTs captures to the package save endpoint. |
| capture-endpoint | string | '/browser-location/capture' | Endpoint used by JS for automatic persistence. |
| collection-name | string | 'default' | Target location collection for automatic save. |
| locationable-type | string | auth user morph class | Override the model class that owns the location (must be allow-listed). |
| locationable-id | mixed | auth user key | Override the model key. |
| event-name | string | 'browser-location:updated' | JS event dispatched on successful capture. |
| error-event-name | string | 'browser-location:error' | JS event dispatched on error. |
| permission-event-name | string | 'browser-location:permission' | JS event dispatched when permission state changes. |
Example with custom options:
<x-browser-location-tracker
button-text="Locate Me"
:auto-capture="false"
:force-permission="false"
:watch="true"
:timeout="10000"
livewire-method="saveLocation"
:required-accuracy-meters="80"
/>
The tracker dispatches these native DOM events on document:
| Event | Description |
| ----------------------------- | -------------------------------------------------------------- |
| browser-location:updated | Successful capture — payload contains full location data |
| browser-location:error | Error — payload contains code and message |
| browser-location:permission | Permission state changed — payload contains state |
| browser-location:saved | Location persisted to DB — payload contains persistence result |
| browser-location:save-error | Persistence request failed |
document.addEventListener("browser-location:updated", (e) => {
console.log(e.detail.latitude, e.detail.longitude);
});
// Last-initialized tracker on the page:
window.BrowserLocationTracker.capture(); // Trigger a fresh capture
window.BrowserLocationTracker.requestPermission(); // Alias for capture()
window.BrowserLocationTracker.getJson(); // Returns latest data as JSON string
// Named instance (if you have multiple trackers):
window.BrowserLocation["browser-location-<ulid>"].capture();
php artisan vendor:publish --tag=browser-location-config
Creates config/browser-location.php. Key options:
'auto_save' => false, // persist every capture automatically
'min_accuracy' => 200, // max accepted accuracy in metres
'prevent_duplicates' => true, // skip saves within 20 m of last point
'default_collection' => 'default',
'component' => [
'auto_capture' => true,
'force_permission' => true,
'watch' => false,
'livewire_method' => 'setBrowserLocation',
],
.env# Provider: google | mapbox | openstreetmap
BROWSER_LOCATION_GEOCODER_PROVIDER=openstreetmap
# Google
BROWSER_LOCATION_GOOGLE_API_KEY=
# Mapbox
BROWSER_LOCATION_MAPBOX_ACCESS_TOKEN=
# OpenStreetMap (required for Nominatim policy compliance)
BROWSER_LOCATION_OSM_USER_AGENT="your-app-name/1.0 ([email protected])"
BROWSER_LOCATION_OSM_EMAIL=[email protected]
# Cache
BROWSER_LOCATION_GEOCODER_CACHE_ENABLED=true
BROWSER_LOCATION_GEOCODER_CACHE_TTL=3600
use Mayaram\BrowserLocation\Facades\Geocoder;
$reverse = Geocoder::reverse(28.6139, 77.2090);
$forward = Geocoder::forward('New Delhi, India');
use Mayaram\BrowserLocation\Contracts\Geocoder;
class CheckoutController
{
public function __construct(private readonly Geocoder $geocoder) {}
public function __invoke(): array
{
return $this->geocoder->reverse(28.6139, 77.2090);
}
}
[
'provider' => 'openstreetmap',
'query' => [...],
'resolved' => [
'formatted_address' => '...',
'latitude' => 28.6139,
'longitude' => 77.209,
'place_id' => '...',
'components' => [...],
],
'results' => [...],
'raw' => [...],
]
.env — never commit keys.user_agent and contact email.Mayaram\BrowserLocation\Exceptions\GeocoderException at your application boundary.HasLocations trait to your modeluse Mayaram\BrowserLocation\Concerns\HasLocations;
class User extends Authenticatable
{
use HasLocations;
}
$user->addLocation($data)->toLocationCollection('checkins');
$order->addLocation($data)->toLocationCollection('delivery');
$user->addLocation($data)->toSingleLocationCollection('live');
$latest = $user->getLatestLocation();
$visits = $user->getLocations('visits');
When <x-browser-location-tracker /> captures a location the package:
POST /browser-location/capturebrowser-location.validate middlewarebrowser_locations and enriches meta with raw GPS, geocoder response, IP, user-agent'auto_save' => false,
'min_accuracy' => 200,
'prevent_duplicates' => true,
'default_collection' => 'default',
'capture_endpoint' => '/browser-location/capture',
// config/browser-location.php
'allowed_locationable_models' => [
App\Models\Order::class,
],
1. Add the trait to your Livewire component:
<?php
namespace App\Livewire;
use Livewire\Component;
use Mayaram\BrowserLocation\Livewire\Concerns\InteractsWithBrowserLocation;
class TripTracker extends Component
{
use InteractsWithBrowserLocation;
public string $status = 'Waiting for location…';
// Called automatically after every location update
public function onBrowserLocationUpdated(array $location): void
{
$this->status = "Lat: {$this->getLatitude()}, Lng: {$this->getLongitude()}";
}
// Optional: return the model that locations should be attached to
public function getBrowserLocationable(): ?\Illuminate\Database\Eloquent\Model
{
return auth()->user();
}
public function render()
{
return view('livewire.trip-tracker');
}
}
2. Add the tracker inside the component's Blade view:
{{-- livewire/trip-tracker.blade.php --}}
<div>
<x-browser-location-tracker /> {{-- wire:ignore is applied automatically --}}
@if ($this->hasLocation())
<p>Lat: {{ $this->getLatitude() }}, Lng: {{ $this->getLongitude() }}</p>
<p>Accuracy: {{ $this->getAccuracy() }} m ({{ $this->getAccuracyLevel() }})</p>
@endif
<p>{{ $status }}</p>
</div>
All methods are available on any Livewire component that uses InteractsWithBrowserLocation:
| Method | Returns | Description |
| ---------------------------------------- | -------- | ------------------------------------------------------------ |
| hasLocation() | bool | true once the browser sends valid coordinates |
| getLatitude() | ?float | Captured latitude |
| getLongitude() | ?float | Captured longitude |
| getAccuracy() | ?float | GPS accuracy in metres |
| getAccuracyLevel() | string | 'excellent' / 'good' / 'poor' / 'unknown' |
| browserLocationIsAccurate(?float $max) | bool | Whether accuracy is within the given or configured threshold |
| Method | Returns | Description |
| --------------------------- | --------- | ---------------------------------------------- |
| getLocationPermission() | ?string | 'granted', 'denied', 'prompt', or null |
| isLocationDenied() | bool | Quick check for denied permission |
| getLocationErrorCode() | ?int | 1 = denied, 2 = unavailable, 3 = timeout |
| getLocationErrorMessage() | ?string | Human-readable error from the browser |
| Method | Description |
| ------------------------------------- | ---------------------------------------------------- |
| setBrowserLocation(array $location) | Receives the payload from JS (called automatically) |
| resetBrowserLocation() | Clears $browserLocation (e.g. after saving a trip) |
| getBrowserLocationJson() | Returns the raw payload as a JSON string |
onBrowserLocationUpdated)Implement this method on your component to react every time a new location arrives:
public function onBrowserLocationUpdated(array $location): void
{
// $location has the same keys as $this->browserLocation
$this->dispatch('location-saved');
}
The trait itself does not include #[Locked] or #[On] because Livewire is an optional dependency. Add them directly on your component for the best security and DX:
use Livewire\Attributes\Locked;
use Livewire\Attributes\On;
// Prevents the client from tampering with $browserLocation via wire:model
#[Locked]
public array $browserLocation = [];
// Allows triggering setBrowserLocation via $dispatch('browser-location:updated', payload)
// in addition to the default direct component.call() mechanism
#[On('browser-location:updated')]
public function setBrowserLocation(array $location): void
{
// Call the trait's implementation:
$this->persistBrowserLocationIfNeeded($location);
$this->browserLocation = $location;
if (method_exists($this, 'onBrowserLocationUpdated')) {
$this->onBrowserLocationUpdated($location);
}
}
Both
#[Locked]and#[On]work identically in Livewire 3 and Livewire 4.
#[On] to react via JS dispatchAn alternative to having JS call setBrowserLocation directly is to dispatch a browser event and let Livewire 4's #[On] handle it:
<x-browser-location-tracker
livewire-method=""
event-name="browser-location:updated"
/>
// In your own JS you can also do:
Livewire.dispatch("browser-location:updated", payload);
#[On('browser-location:updated')]
public function setBrowserLocation(array $location): void { ... }
| Laravel version | Support | | ---------------- | :-----: | | 10.x | ✅ | | 11.x | ✅ | | 12.x | ✅ | | 13.x | ✅ |
| Feature | Plain Blade | Livewire 3 | Livewire 4 |
| --------------------------------------- | :---------: | :--------: | :--------: |
| <x-browser-location-tracker> | ✅ | ✅ | ✅ |
| Auto-capture on page load | ✅ | ✅ | ✅ |
| Hidden form inputs | ✅ | ✅ | ✅ |
| wire:ignore prevents DOM morphing | — | ✅ | ✅ |
| JS calls Livewire method on capture | — | ✅ | ✅ |
| Re-capture after Livewire navigate | — | ✅ | ✅ |
| InteractsWithBrowserLocation helpers | — | ✅ | ✅ |
| #[Locked] / #[On] on your component | — | ✅ | ✅ |
Route::post('/checkout', CheckoutController::class)
->middleware('browser-location.validate');
The middleware accepts location from:
latitude, longitude, …)location object in the request bodyX-Browser-Location JSON headercomposer test
MIT