syriable / laravel-user-context
User presence, timezone awareness, locale context and login metadata for Laravel — know who is online, where they are, and what time it is for them.
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/contracts: ^11.0||^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- barryvdh/laravel-debugbar: ^4.2
- fakerphp/faker: ^1.24.1
- geoip2/geoip2: ^3.1
- larastan/larastan: ^3.9.6
- laravel/boost: ^2.4
- laravel/pao: ^1.0.6
- laravel/pint: dev-feat/blade-support
- laravel/sail: ^1.58.0
- mockery/mockery: ^1.6.12
- nunomaduro/collision: ^8.9.4
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.7.0
- pestphp/pest-plugin-laravel: ^4.1.0
- pestphp/pest-plugin-type-coverage: ^4.0.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- rector/rector: ^2.4.2
Suggests
- geoip2/geoip2: Required to use the MaxMind GeoLite2 local database geolocation driver (^3.0).
This package is auto-updated.
Last update: 2026-07-19 07:17:13 UTC
README
Know who is online, where they are, and what time it is for them — presence, timezone awareness, locale context and login metadata for any Laravel app.
A lightweight, database-driven foundation package. No Redis, no heavy dependencies — presence works without a queue worker; geolocation lookups are queued by default so they never block the request path.
$user->isOnline(); // true $user->presence()->lastSeen(); // CarbonImmutable|null $user->location()->countryName(); // "Sweden" (from ISO country code) $user->timezone()->now(); // CarbonImmutable in the user's zone $user->greeting(); // "Good afternoon" // A user in New York checking the best time to message a user in Shanghai: $comparison = $newYorkUser->timeFor($shanghaiUser); $comparison->formattedOffset(); // "+12:00" $comparison->isNight(); // true → maybe wait $comparison->isConvenientTime(); // false → not a good time to ping
Requirements
- PHP 8.3+
- Laravel 11, 12 or 13
Installation
composer require syriable/laravel-user-context
Publish and run the migrations:
php artisan vendor:publish --tag="laravel-user-context-migrations"
php artisan migrate
Optionally publish the config file:
php artisan vendor:publish --tag="laravel-user-context-config"
Add the trait to your authenticatable model:
use Syriable\UserContext\Concerns\HasUserContext; class User extends Authenticatable { use HasUserContext; }
Register the tracking middleware so activity is recorded on each request. In
bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \Syriable\UserContext\Http\Middleware\TrackUserContext::class, ]); })
That's it — logins, logouts, presence, locale and (optionally) geolocation are now tracked automatically.
Documentation
Publish the config (php artisan vendor:publish --tag="laravel-user-context-config")
and read the comments in config/user-context.php for every key. Notable defaults:
| Key | Default | Why |
|---|---|---|
ip.privacy |
anonymize |
Store /24 (IPv4) / /48 (IPv6) — not raw IPs |
geolocation.driver |
null |
No external lookup until you opt into ipinfo / maxmind / ipapi |
queue.enabled |
true |
Geo lookups never block the request that recorded activity |
routes.middleware |
web, auth, throttle:60,1 |
Heartbeat is auth-gated and rate-limited |
use Syriable\UserContext\Facades\UserContext; UserContext::isOnline($user); UserContext::timezoneFor($user)->now(); UserContext::for($user); // ContextSnapshot
Usage at a glance
Presence
$user->isOnline(); // bool $user->presence()->status(); // "online" | "offline" $user->presence()->lastSeen(); // ?CarbonImmutable $user->presence()->lastLogin(); // ?CarbonImmutable UserContext::online()->count(); // query builder over online users
Keep a browser tab "online" with the bundled heartbeat component:
<x-user-context::heartbeat />
Presence source
Laravel's database session driver already records last_activity,
ip_address and user_agent for the authenticated user on every request.
Rather than duplicate that, the package can read presence straight from
Laravel's own sessions table. This is controlled by presence.source:
| Value | Behavior |
|---|---|
auto (default) |
Read from sessions when the database session driver is active and the table exists; otherwise fall back to the package's own columns. |
sessions |
Always read from Laravel's sessions table (requires the database session driver). |
table |
Always read from the package's user_contexts columns — works on every session driver, supports polymorphic user models, and keeps the package's IP privacy modes. |
auto targets applications with a single authenticatable type. Presence
reads (isOnline(), presence()->lastSeen(), location()->ipAddress(),
location()->userAgent()) flow through the active source; login/logout
timestamps, timezone, locale and geolocation always come from the package's
own table, since Laravel's sessions table does not record them. The
package still writes its own columns regardless of this setting, so
switching sources never loses data.
Note
In sessions mode location()->ipAddress() returns the raw address
Laravel stores on the session row; the user-context.ip.privacy modes
(anonymize / hash / discard) apply only in table mode. Multi-guard or
polymorphic setups should use the table source, because Laravel's
sessions.user_id has no morph type.
Timezone & locale
$user->timezone()->name(); // "Asia/Shanghai" (or null if unknown) $user->localTime(); // CarbonImmutable in their timezone $user->isNight(); // bool $user->greeting(); // localized "Good evening" $user->locale(); // "en_US" // Explicit overrides always win over IP / header detection: UserContext::overrideTimezone($user, 'Europe/Berlin'); UserContext::overrideLocale($user, 'de');
Location
$user->location()->countryCode(); // "SE" $user->location()->countryName(); // "Sweden" $user->location()->city(); // "Stockholm" $user->location()->ipAddress(); // last known IP (raw in sessions mode) $user->location()->userAgent(); // last known User-Agent, or null
User-to-user time comparison
$c = $userA->timeFor($userB); $c->theirTime; // CarbonImmutable in B's timezone $c->formattedOffset(); // "+12:00" $c->dayPeriod; // DayPeriod::Night $c->isConvenientTime(); // is it a reasonable local hour to contact B?
Blade components
<x-user-context::user-presence :user="$user" /> <x-user-context::local-time :user="$user" format="H:i" /> <x-user-context::heartbeat />
API
GET /user-context/me returns the authenticated user's context:
{
"online": true,
"last_seen": "2026-07-18T10:30:00+00:00",
"timezone": "Europe/Stockholm",
"local_time": "15:30",
"country": "Sweden",
"locale": "sv_SE"
}
Events
Listen for any of: UserOnline, UserOffline, UserLocationUpdated,
UserTimezoneChanged, UserLoginRecorded. See Extending.
Testing
composer test
Changelog
Please see CHANGELOG for what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.