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

Laravel Seeable Laravel Package

zepfietje/laravel-seeable

Laravel Seeable adds seen/unseen tracking to your Eloquent models. Attach the trait to let users mark records as seen, check whether an item has been viewed, and query unseen records per user—handy for notifications, activity feeds, and read receipts.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install: composer require zepfietje/laravel-seeable
  2. Publish the migration (adds last_seen_at nullable timestamp to users) and run it:
    php artisan vendor:publish --tag="seeable-migrations"
    php artisan migrate
    
  3. Add the Seeable trait to your User model:
    use Zepfietje\Seeable\Seeable;
    
    class User extends Authenticatable
    {
        use Seeable;
    }
    
  4. Register the middleware. On Laravel ≤10, add it to app/Http/Kernel.php in the web group (or a named group for authenticated routes):
    protected $middlewareGroups = [
        'web' => [
            // ...
            \Zepfietje\Seeable\Middleware\RecordUserLastSeen::class,
        ],
    ];
    
    On Laravel 11+, register it in bootstrap/app.php via ->withMiddleware(...).

First use case: the "Last seen 12 minutes ago" line on a profile or admin user list. With the trait applied, $user->last_seen_at is a Carbon instance, so $user->last_seen_at?->diffForHumans() is the one-liner you ship. The package is deliberately minimal — there is no model, no config file, no dashboard; it is essentially a migration, a trait, and a middleware.

Implementation Patterns

Show activity in API resources and Blade. Cast already comes from the trait, so in an API resource:

return [
    'name' => $this->name,
    'last_seen_at' => $this->last_seen_at?->toIso8601String(),
];

And in Blade: {{ $user->last_seen_at?->diffForHumans() ?? 'Never' }}.

Scope to a route group, not globally. Writing a timestamp on every web request is wasteful; attach the middleware to your authenticated group:

Route::middleware(['web', 'auth', RecordUserLastSeen::class])
    ->group(function () { /* ... */ });

Build an "online now" query. Since last_seen_at is a normal column, you can filter cheaply:

User::where('last_seen_at', '>=', now()->subMinutes(5))->count();

Wrap it in a local scope (scopeOnline, scopeInactive) for reuse across admin dashboards.

Throttle writes to reduce DB load. The middleware writes on each request by default (depending on version behavior). At scale, either (a) put the middleware on a low-traffic group, (b) guard it yourself in a custom middleware that only updates when last_seen_at is older than N minutes, or (c) move the update into a queued job / dispatchAfterResponse():

if (! $user->last_seen_at || $user->last_seen_at->lt(now()->subMinutes(5))) {
    $user->forceFill(['last_seen_at' => now()])->saveQuietly();
}

saveQuietly() avoids touching updated_at and skipping model events — important if you already use those.

Combine with session or activity tables. For richer presence, use this as the lightweight "last touch" and layer on an activities table or session driver for "current page" context.

Backfill on the fly. Existing users will have last_seen_at = null until their next request; treat null as "never seen" in UI rather than "long ago."

Gotchas and Tips

  • Archived package. Last release was Feb 2023 and the repo is archived. Do not expect Laravel 11/12 fixes or PHP 8.3+ updates. Install it, verify the middleware boots on your framework version, and vendor/fork it if you need to modify.
  • Middleware order matters. The middleware must run after auth has resolved the user (otherwise $request->user() is null). Place it after auth in the stack, not before.
  • Guests are skipped. No User, no timestamp — that's expected; don't rely on it for anonymous traffic analytics.
  • Different user table/model. The published migration assumes users. If your guard uses another table (e.g. admins), edit the migration before running it, and confirm the trait reads/writes last_seen_at on that model.
  • Mass assignment. last_seen_at is usually not in $fillable — assign it with forceFill() or a direct property set, not create()/update().
  • Timezone handling. Store in UTC (default) and let Carbon cast to the app timezone; don't manually ->setTimezone() before saving or you'll corrupt ordering when servers span zones.
  • updated_at churn. If you override the middleware to write frequently, use saveQuietly() or timestamps = false; otherwise updated_at becomes meaningless on the users table and can trigger observers.
  • Debounce on write-heavy endpoints. A polling endpoint that hits a route with this middleware will hammer the DB. Prefer a route-level or cache-based throttle (Cache::add("seen:{$id}", true, 300)) over a write per request.
  • Extending without the package. If the archived status worries you, the whole thing is ~30 lines: a migration, a nullable timestamp column, a trait with $casts = ['last_seen_at' => 'datetime'], and a middleware that does $request->user()?->forceFill(...)->saveQuietly(). Copying it into your app is often a better choice than pinning a dead dependency.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums