{"id":382442,"date":"2026-10-11T15:25:49","date_gmt":"2026-10-11T15:25:49","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/shrikant-visitor-tracker\/"},"modified":"2026-10-11T15:53:06","modified_gmt":"2026-10-11T15:53:06","slug":"shrikant-visitor-tracker","status":"publish","type":"plugin","link":"https:\/\/lug.wordpress.org\/plugins\/shrikant-visitor-tracker\/","author":23318666,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"7.1.3","requires":"6.3","requires_php":"8.2","requires_plugins":null,"header_name":"Shrikant Visitor Tracker","header_author":"Shri Kant Gaur","header_description":"Visitor analytics that counts correctly behind a page cache, and imports your old view counter's history instead of throwing it away.","assets_banners_color":"273447","last_updated":"2026-10-11 15:53:06","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/shrikantgaur\/shrikant-visitor-tracker","header_author_uri":"https:\/\/profiles.wordpress.org\/shrikantgaur\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":53,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"shrikantgaur","date":"2026-10-11 15:53:06","revision":3739731}},"upgrade_notice":{"1.0.0":"<p>Initial release \u2014 activate and visit SK Analytics in your admin sidebar.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3739684,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3739684,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3739684,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3739684,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3739731,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=shrikant-visitor-tracker\",\"preferredVersions\":{\"php\":\"8.2\",\"wp\":\"latest\"},\"phpExtensionBundles\":[\"kitchen-sink\"],\"login\":true,\"steps\":[{\"step\":\"runPHP\",\"code\":\"<?php require_once '\\\/wordpress\\\/wp-load.php'; \\\/* A preview of an analytics plugin with no analytics in it shows nothing at all, so six weeks of plausible traffic is written straight into the plugin's own tables and then aggregated by the plugin's own code. Wrapped, because a preview that loads with an empty dashboard is still a preview, while one that dies on a fatal is not. *\\\/ try { global $wpdb; $raw = $wpdb->prefix . 'shrikant_visitor_analytics'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $raw ) ) !== $raw ) { return; } $pages = []; foreach ( [ 'Getting started', 'Pricing', 'Docs: installation', 'Blog: what changed in 2.0', 'Contact' ] as $i => $title ) { $pages[] = wp_insert_post( [ 'post_title' => $title, 'post_type' => 'post', 'post_status' => 'publish', 'post_content' => 'Sample content for the preview.' ] ); } $devices = [ 'desktop', 'desktop', 'desktop', 'mobile', 'mobile', 'tablet' ]; $browsers = [ 'Chrome', 'Chrome', 'Chrome', 'Safari', 'Firefox', 'Edge' ]; $systems = [ 'Windows', 'macOS', 'Android', 'iOS', 'Linux' ]; $sources = [ 'direct', 'search', 'search', 'social', 'referral' ]; $countries = [ 'IN', 'IN', 'US', 'GB', 'DE', 'AU' ]; $rows = []; for ( $back = 41; $back >= 0; $back-- ) { $date = gmdate( 'Y-m-d', strtotime( \\\"-{$back} days\\\" ) ); $weekday = (int) gmdate( 'N', strtotime( $date ) ); $visits = ( $weekday >= 6 ) ? wp_rand( 8, 22 ) : wp_rand( 18, 52 ); for ( $v = 0; $v < $visits; $v++ ) { $hour = wp_rand( 6, 23 ); $i = wp_rand( 0, 5 ); $src = $sources[ wp_rand( 0, 4 ) ]; $rows[] = $wpdb->prepare( '(%s,%s,%d,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%s)', 'v' . wp_rand( 1, 420 ), 's' . wp_rand( 1, 900 ), $pages[ wp_rand( 0, 4 ) ], $date, $hour, $date . ' ' . sprintf( '%02d', $hour ) . ':' . sprintf( '%02d', wp_rand( 0, 59 ) ) . ':00', $countries[ wp_rand( 0, 5 ) ], $devices[ $i ], $browsers[ $i ], $systems[ wp_rand( 0, 4 ) ], $src, 'search' === $src ? 'https:\\\/\\\/www.google.com\\\/' : '', 'search' === $src ? 'google' : ( 'social' === $src ? 'twitter' : '' ), 'search' === $src ? 'organic' : ( 'social' === $src ? 'social' : '' ), wp_rand( 0, 1 ), '\\\/sample-page\\\/' ); } } foreach ( array_chunk( $rows, 200 ) as $chunk ) { $wpdb->query( 'INSERT INTO ' . $raw . ' (visitor_id,session_id,page_id,visit_date,visit_hour,visit_time,country_code,device_type,browser,os,referrer_type,referrer_url,utm_source,utm_medium,is_unique,page_url) VALUES ' . implode( ',', $chunk ) ); } $plugin = Shrikant_Visitor_Tracker::get_instance(); $cron = $plugin->get( 'cron' ); if ( $cron && method_exists( $cron, 'rebuild_summaries' ) ) { $cron->rebuild_summaries(); } } catch ( Throwable $e ) { \\\/* The dashboard still loads; it simply has nothing in it. *\\\/ }\"},{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"shrikant-visitor-tracker\"},\"options\":{\"activate\":true}}]}"}},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3739684,"resolution":"1","location":"assets","locale":"","width":1280,"height":1400},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3739684,"resolution":"2","location":"assets","locale":"","width":1280,"height":1050},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3739684,"resolution":"3","location":"assets","locale":"","width":1280,"height":950},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3739684,"resolution":"4","location":"assets","locale":"","width":1280,"height":1250},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3739684,"resolution":"5","location":"assets","locale":"","width":1280,"height":850}},"screenshots":{"1":"The dashboard: totals for today, the week and the month, the trend over a\nyear, and the split by device, browser, operating system and source.","2":"Pages report \u2014 which pages were read, by how many people, sortable and\nsearchable, with a share of the total.","3":"UTM campaigns, so a link you posted somewhere can be told apart from\nsearch traffic.","4":"Settings. Nothing is collected until you say so: country lookup is off\nuntil switched on, and the view count can be shown on any post type.","5":"Import from WP-PostViews or Post Views Counter, with a count of what will\nbe brought across before anything is written."}},"plugin_section":[262246],"plugin_tags":[232,131785,396,521,871],"plugin_category":[36,54],"plugin_contributors":[244794],"plugin_business_model":[],"class_list":["post-382442","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-analytics","plugin_tags-gdpr","plugin_tags-privacy","plugin_tags-statistics","plugin_tags-visitors","plugin_category-analytics","plugin_category-security-and-spam-protection","plugin_contributors-shrikantgaur","plugin_committers-shrikantgaur"],"banners":{"banner":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/banner-772x250.png?rev=3739684","banner_2x":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/banner-1544x500.png?rev=3739684","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/icon-128x128.png?rev=3739684","icon_2x":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/icon-256x256.png?rev=3739684","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/screenshot-1.png?rev=3739684","caption":"The dashboard: totals for today, the week and the month, the trend over a\nyear, and the split by device, browser, operating system and source."},{"src":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/screenshot-2.png?rev=3739684","caption":"Pages report \u2014 which pages were read, by how many people, sortable and\nsearchable, with a share of the total."},{"src":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/screenshot-3.png?rev=3739684","caption":"UTM campaigns, so a link you posted somewhere can be told apart from\nsearch traffic."},{"src":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/screenshot-4.png?rev=3739684","caption":"Settings. Nothing is collected until you say so: country lookup is off\nuntil switched on, and the view count can be shown on any post type."},{"src":"https:\/\/ps.w.org\/shrikant-visitor-tracker\/assets\/screenshot-5.png?rev=3739684","caption":"Import from WP-PostViews or Post Views Counter, with a count of what will\nbe brought across before anything is written."}],"raw_content":"<!--section=description-->\n<p><strong><a href=\"https:\/\/playground.wordpress.net\/?plugin=shrikant-visitor-tracker&amp;url=\/wp-admin\/admin.php%3Fpage%3Dshrikant-visitor-tracker\">Try it now, without installing anything<\/a><\/strong> \u2014 opens a throwaway WordPress in your browser with the plugin already running. Nothing is installed on your own site.<\/p>\n\n<p>Most analytics plugins assume you are starting from nothing. This one assumes you are not.<\/p>\n\n<h4>Switching counters without losing the history<\/h4>\n\n<p>If you run Post Views Counter or WP-PostViews, deleting it throws away every number it ever recorded. The <strong>Import<\/strong> screen reads those totals across first \u2014 reading only, never writing to or removing anything in them \u2014 so you can switch and keep the figures.<\/p>\n\n<p>Imported counts are then kept <strong>separate and labelled, never merged<\/strong> into this plugin's own statistics. The two measure different things: one spans years under its own rules, the other starts the day you switch this on and leaves out the bots it recognises. Adding them together would produce a number that means nothing in particular. So a reader sees the two added up and no post appears to lose its history, while the dashboard shows only what was actually tracked and stays comparable with itself.<\/p>\n\n<p>Post Views Counter and WP-PostViews are separate projects by their own authors. They are named here only so their data can be read; this plugin is not affiliated with or endorsed by them.<\/p>\n\n<h4>Nothing is deleted unless you ask<\/h4>\n\n<p><strong>Deleting this plugin does not delete its data<\/strong>, unless you switch that on in Settings. Replacing a hand-installed copy with the directory one goes through the same Delete button as saying goodbye to it, and that should not cost you six months of history.<\/p>\n\n<p>The hourly summaries every report is built from are never deleted at all, by anything. Individual visit rows are kept for good too, unless you choose a retention period yourself.<\/p>\n\n<h4>Why these numbers are lower than your old counter's<\/h4>\n\n<p>Most view counters add up in PHP while the page is being built. On a site with a page cache that means the readers served from cache are never counted at all, while the crawlers that bypass the cache are counted every time \u2014 so the number drifts away from reality in both directions at once.<\/p>\n\n<p>This one counts from the browser, after the page has loaded, and filters known bots before recording anything. Expect its figures to sit closer to what Search Console reports than to a counter that has been accumulating for years under different rules.<\/p>\n\n<h4>Everything else<\/h4>\n\n<p>Shrikant Visitor Tracker gives you complete control over your visitor analytics data \u2014 everything is stored in your own database, nothing leaves your server (except a geo-IP lookup to the free ipwho.is API, which is cached for at least 24 hours).<\/p>\n\n<h4>Key Features<\/h4>\n\n<ul>\n<li><strong>Total \/ daily \/ weekly \/ monthly \/ yearly visitors<\/strong> with page-level tracking<\/li>\n<li><strong>Device, browser, and OS detection<\/strong> via lightweight User-Agent parsing (no library)<\/li>\n<li><strong>Referrer categorisation<\/strong> (direct, search, social, other) + full <strong>UTM parameter tracking<\/strong><\/li>\n<li><strong>Country detection, off by default<\/strong> \u2014 turn it on and the anonymised IP (never the full one) goes to ipwho.is for a country code, cached 24h. This is the only outside request the plugin can make<\/li>\n<li><strong>Real-time \"currently online\" counter<\/strong> using WordPress transients<\/li>\n<li><strong>Counts correctly behind a page cache<\/strong> \u2014 a non-blocking browser beacon, not a PHP counter, so WP Rocket, LiteSpeed, W3 Total Cache and Cloudflare do not hide your readers<\/li>\n<li><strong>Pre-aggregated summary table<\/strong> \u2014 every report is built from hourly summaries, so a report stays fast whatever the traffic, and the summaries are kept for good<\/li>\n<li><strong>Bot filtering<\/strong> \u2014 user-agent matching against a maintained list, plus proxy and prefetch headers, before anything is recorded<\/li>\n<li><strong>Privacy-first<\/strong> \u2014 IP anonymisation, DNT header respect, cookie consent hook, GDPR data export\/erasure<\/li>\n<li><strong>REST API<\/strong> \u2014 full stats API under <code>\/wp-json\/shrikant-vt\/v1\/<\/code> for headless or custom integrations<\/li>\n<li><strong>WP-CLI commands<\/strong> \u2014 <code>wp shrikant-vt stats today<\/code>, <code>wp shrikant-vt export<\/code>, <code>wp shrikant-vt cleanup --dry-run<\/code>, and more<\/li>\n<li><strong>Import from Post Views Counter or WP-PostViews<\/strong> \u2014 switch without losing the history<\/li>\n<li><strong>Any date range<\/strong> \u2014 the usual 7\/30\/90-day presets, or two dates of your own<\/li>\n<li><strong>Searchable, sortable, paginated reports<\/strong> with CSV export<\/li>\n<li><strong>Choose where the count appears<\/strong> \u2014 a checkbox per public post type, not just posts<\/li>\n<li><strong>Nothing is ever deleted<\/strong> unless you ask for it \u2014 not on uninstall, not on a schedule<\/li>\n<li><strong>Zero paid dependencies<\/strong> \u2014 100% free and open source, nothing loaded from a CDN<\/li>\n<\/ul>\n\n<h4>Third-party code<\/h4>\n\n<p>This plugin bundles Chart.js 4.5.1 (https:\/\/www.chartjs.org), used to draw the\ndashboard charts. Chart.js is released under the MIT licence, which is\ncompatible with the GPL, and its source is at https:\/\/github.com\/chartjs\/Chart.js.\nIt is bundled rather than loaded from a CDN because a plugin in this directory\nmay not load code from elsewhere at run time. Nothing else is bundled, and no\nother third-party code is loaded.<\/p>\n\n<h4>Privacy &amp; GDPR<\/h4>\n\n<ul>\n<li>Raw IP addresses are <strong>never stored<\/strong><\/li>\n<li>With IP anonymisation enabled (default), the last IPv4 octet is zeroed before any geo lookup<\/li>\n<li>Visitor IDs are HMAC-SHA256 hashes that rotate daily \u2014 they cannot be reversed to identify real people<\/li>\n<li>DNT header is respected by default<\/li>\n<li>Integrates with any cookie consent plugin via the <code>shrikant_vt_has_consent<\/code> filter<\/li>\n<li>Compatible with WordPress's built-in personal data export\/erasure tools<\/li>\n<\/ul>\n\n<h4>Developer Hooks<\/h4>\n\n<ul>\n<li><code>shrikant_vt_before_track_visit<\/code> \u2014 modify or abort tracking<\/li>\n<li><code>shrikant_vt_after_insert<\/code> \u2014 react to new visit rows<\/li>\n<li><code>shrikant_vt_has_consent<\/code> \u2014 integrate cookie consent plugins<\/li>\n<li><code>shrikant_vt_is_bot<\/code> \u2014 override bot detection<\/li>\n<li><code>shrikant_vt_get_stats<\/code> \u2014 filter any stats result<\/li>\n<li><code>shrikant_vt_default_settings<\/code> \u2014 add custom settings<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>sk-visitor-tracker<\/code> folder to <code>\/wp-content\/plugins\/<\/code><\/li>\n<li>Activate the plugin through the <strong>Plugins<\/strong> menu in WordPress<\/li>\n<li>Navigate to <strong>SK Analytics<\/strong> in the admin sidebar to view your dashboard<\/li>\n<li>Visit <strong>SK Analytics \u2192 Settings<\/strong> to configure tracking options<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20plugin%20share%20data%20with%20third%20parties%3F\"><h3>Does this plugin share data with third parties?<\/h3><\/dt>\n<dd><p>Not unless you switch country detection on, and it ships off.<\/p>\n\n<p>With it on, the anonymised IP address (last octet zeroed) is sent over https to ipwho.is, operated by ipwhois.io, which returns a two-letter country code and nothing else. Terms: https:\/\/ipwhois.io\/terms \u2014 Privacy: https:\/\/ipwhois.io\/privacy. The result is cached for at least 24 hours. Nothing else about a visitor ever leaves the site, and switching the setting back off stops it entirely. The result is cached for at least 24 hours. All other data stays on your server.<\/p><\/dd>\n<dt id=\"is%20this%20compatible%20with%20caching%20plugins%3F\"><h3>Is this compatible with caching plugins?<\/h3><\/dt>\n<dd><p>Yes \u2014 enable <strong>Async Tracking<\/strong> in settings (it's on by default). The tracking pixel fires client-side via XMLHttpRequest after the cached page loads in the visitor's browser.<\/p><\/dd>\n<dt id=\"how%20does%20uniqueness%20work%3F\"><h3>How does uniqueness work?<\/h3><\/dt>\n<dd><p>Each visitor gets a long-lived cookie (2 years, HttpOnly, Secure, SameSite=Lax). A visit is counted as \"unique\" once per visitor per day using a transient-based dedup gate \u2014 no SELECT query on every page view.<\/p><\/dd>\n<dt id=\"is%20it%20compatible%20with%20wordpress%20multisite%3F\"><h3>Is it compatible with WordPress Multisite?<\/h3><\/dt>\n<dd><p>Yes. Each sub-site gets its own prefixed database tables and settings.<\/p><\/dd>\n<dt id=\"can%20i%20use%20this%20with%20wp-cli%3F\"><h3>Can I use this with WP-CLI?<\/h3><\/dt>\n<dd><p>Yes. Run <code>wp shrikant-vt stats today<\/code>, <code>wp shrikant-vt top-pages --limit=20<\/code>, <code>wp shrikant-vt export --from=2025-01-01 --file=visits.csv<\/code>, and more.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<\/ul>","raw_excerpt":"Visitor analytics that counts correctly behind a page cache, and imports your old view counter's history instead of throwing it away.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/382442","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=382442"}],"author":[{"embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/shrikantgaur"}],"wp:attachment":[{"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=382442"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=382442"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=382442"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=382442"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=382442"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/lug.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=382442"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}