jeffersongoncalves/laravel-page-visits

Track page-view visits on any Laravel app's public routes, with device/browser/geo/locale/referer/UTM capture
1,094 2
Install
composer require jeffersongoncalves/laravel-page-visits
Latest Version:1.3.0
PHP:^8.2
Laravel:12 · 13
License:MIT
Last Updated:Oct 10, 2026
Links: GitHub  ·  Packagist
Maintainer: jeffersongoncalves

Laravel Page Visits

Laravel Page Visits

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads License

Tracks page-view visits on any Laravel app's public GET routes: one row per page hit, capturing device/browser/GeoIP/locale/referer/UTM safely — anonymized IP, bot flagging, and GDPR export/ erasure — on top of jeffersongoncalves/laravel-visitor-fingerprint for every fingerprinting primitive.

Installation

You can install the package via composer:

composer require jeffersongoncalves/laravel-page-visits

Publish the config file and run the migration:

php artisan vendor:publish --tag="page-visits-config"
php artisan migrate

Usage

Once installed, the package automatically appends TrackPageVisit to the web middleware group (disable via page-visits.auto_register_middleware) and every GET request not matched by page-visits.exclude gets a page_visits row — no further setup required.

use JeffersonGoncalves\LaravelPageVisits\Models\PageVisit;

PageVisit::query()->latest('visited_at')->first();

GDPR / LGPD export and erasure

use JeffersonGoncalves\LaravelPageVisits\Compliance\PageVisitPersonalDataService;

$service = new PageVisitPersonalDataService;

$service->exportForIp($ip);  // matching rows as arrays
$service->forgetForIp($ip);  // nulls ip_hash/ip_anonymized/ip_version/user_agent_hash in place

Daily aggregation and retention pruning

page_visits grows unbounded otherwise. The package self-schedules page-visits:aggregate-and-prune daily at 02:30 (configurable via page-visits.scheduling.aggregate_and_prune) — no setup needed, as long as the host app's scheduler is running (schedule:run on cron, or schedule:work locally). It folds each day's raw rows into page_visit_daily_stats and deletes raw rows older than page-visits.retention_days.

Opt out (e.g. to run it yourself with different args) via config or env:

'scheduling' => [
    'aggregate_and_prune' => [
        'enabled' => false, // PAGE_VISITS_SCHEDULE_AGGREGATE_AND_PRUNE
    ],
],

Run it for a specific day (e.g. to backfill) with --date:

php artisan page-visits:aggregate-and-prune --date=2026-09-01

page_visit_daily_stats holds one row per date with visits_count, unique_visits_count, bot_visits_count, plus JSON breakdowns (device_stats, browser_stats, os_stats, country_stats, city_stats, referer_stats, referer_type_stats, utm_source_stats, utm_medium_stats, utm_campaign_stats, language_stats, hourly_stats) — query it directly via DB::table(config('page-visits.daily_stats_table')) for "last 7/30/90 days" reporting instead of scanning raw rows.

response_time_stats holds that day's server response times in milliseconds (bots included): {"count": 1200, "avg": 180, "min": 12, "max": 1654, "p50": 140, "p95": 520, "p99": 980}, or null when no visit was timed. Upgrading from 1.2.x? Republish and run the new migration:

php artisan vendor:publish --tag="page-visits-migrations"
php artisan migrate

Configuration

// config/page-visits.php
return [
    'table' => 'page_visits',
    'daily_stats_table' => 'page_visit_daily_stats',

    'track_ip_address' => true,
    'track_browser' => true,
    'track_browser_version' => true,
    'track_operating_system' => true,
    'track_operating_system_version' => true,
    'track_device_type' => true,
    'track_referer_url' => true,
    'track_browser_language' => true,

    'exclude' => [
        'livewire/update', 'admin/*', 'app/*', 'horizon/*', '_debugbar/*',
        'sw.js', 'manifest.json', 'favicon-proxy', 'up', 'og/*',
    ],

    'auto_register_middleware' => true,

    'retention_days' => env('PAGE_VISITS_RETENTION_DAYS', 400),

    'scheduling' => [
        'aggregate_and_prune' => [
            'enabled' => env('PAGE_VISITS_SCHEDULE_AGGREGATE_AND_PRUNE', true),
            'time' => env('PAGE_VISITS_SCHEDULE_AGGREGATE_AND_PRUNE_TIME', '02:30'),
        ],
    ],

    'compliance' => [
        'analytics_only' => env('PAGE_VISITS_ANALYTICS_ONLY', false),
    ],
];

Every track_* toggle nulls out its own column instead of skipping the row — the visit is always recorded, just with less detail when a toggle is off. compliance.analytics_only additionally nulls every PII column (ip_hash, ip_anonymized, ip_version, user_agent_hash).

daily_stats_table and retention_days are read by page-visits:aggregate-and-prune — see Daily aggregation and retention pruning above.

GeoIP resolution, bot detection, IP anonymization, and referer classification are all delegated to jeffersongoncalves/laravel-visitor-fingerprint — see its README to configure the GeoIP driver (headers/ip_api/maxmind).

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email the author instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Related Packages

codeaken/laravel-piwiktracker

Insertion of Piwik tracking code and a hidden mode for hiding your tracking serv...

100 3
ronald2wing/laravel-ga4

A lightweight Laravel package for seamless Google Analytics 4 (GA4) integration...

150 0
cornford/googlitics

An easy way to intergrate Google Analytics with Laravel.

10,219 32
irazasyed/laravel-gamp

Send analytics data to Google Analytics from Laravel. A package for GA Measureme...

1,614,517 331
kerkness/fa-wired

Fathom Analytic Events for Laravel/Livewire

391 16

Version History

Version Released PHP Laravel License
1.3.0 ^8.2 ^12.0|^13.0 MIT
1.2.0 ^8.2 ^12.0|^13.0 MIT
1.1.0 ^8.2 ^12.0|^13.0 MIT
1.0.3 ^8.2 ^12.0|^13.0 MIT
1.0.2 ^8.2 ^12.0|^13.0 MIT