jeffersongoncalves / laravel-page-visits
Track page-view visits on any Laravel app's public routes, with device/browser/geo/locale/referer/UTM capture
Package info
github.com/jeffersongoncalves/laravel-page-visits
pkg:composer/jeffersongoncalves/laravel-page-visits
Requires
- php: ^8.2
- illuminate/contracts: ^12.0|^13.0
- jeffersongoncalves/laravel-visitor-fingerprint: ^1.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.24
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-13 00:32:52 UTC
README
Laravel Page Visits
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.
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.
