AIPromptTheory Suite · Free & Educational
Insight.
See what your content is really doing. A full post-views & analytics plugin for WordPress — and the complete, step-by-step guide to how we built it, so you can build (and brand) your own.
The pitch
Everything Insight does
Every feature below ships in the free plugin — and every one of them is explained in the build chapters that follow. That's the deal: you get the tool, and you get to know exactly how it works.
📈 Tracking
- Views for posts, pages & any custom post type
- Unique visitors vs. total visits (configurable window)
- Bot & crawler filtering (extensible signature list)
- Three counting modes: AJAX beacon, REST, server-side PHP
- Referrer, device, browser & language capture
- Author-level & taxonomy-level aggregation
📊 Reporting
- WP dashboard widget: stat tiles, monthly chart, ‹month› nav, top-posts table
- Full analytics screen: views-over-time with range filters
- Device & browser doughnut charts, referrer tables
- Week-vs-week period comparison with delta
- Rising / Falling traffic signals per post
- CSV & XML export of everything
🧠 The novel ten
- AI content insights in plain English
- GitHub-style heat-map calendar
- Content decay alerts with refresh action
- Milestone notifications (100 / 1K / 10K…)
- Scroll-based reading-depth tracking
- Share-to-view virality score
- Weekly/monthly top-content email digest
- SVG view-count badges per post
- GDPR kit: IP anonymisation, cookieless mode, retention, purge
- Public leaderboard shortcode
🔌 Integrations
[post_views]+ leaderboard & most-viewed shortcodes- Gutenberg block, Elementor widget, Divi module
- REST API (
/aipt-insight/v1/…) & WP-CLI commands - Sidebar widget + theme template tags
- Object-cache friendly, custom-table storage, multisite-aware
- Dark/light admin theme that remembers your choice
Chapter 1
Architecture: one container, many small classes
Insight is a classic object-oriented WordPress plugin: a tiny bootstrap file, a singleton service container, and one focused class per concern. No frameworks, no builders — just WordPress APIs used the way they were designed to be used.
define( 'AIPT_PVA_VERSION', '1.1.0' );
define( 'AIPT_PVA_DIR', plugin_dir_path( __FILE__ ) );
require_once AIPT_PVA_DIR . 'includes/class-aipt-insight.php';
// Classes needed at (de)activation, BEFORE plugins_loaded fires:
require_once AIPT_PVA_DIR . 'includes/class-aipt-insight-settings.php';
require_once AIPT_PVA_DIR . 'includes/class-aipt-insight-install.php';
register_activation_hook( __FILE__, [ 'AIPT_PVA_Install', 'activate' ] );
function aipt_pva() { return AIPT_PVA::instance(); }
add_action( 'plugins_loaded', 'aipt_pva', 5 );plugins_loaded… but WordPress fires the
activation hook before plugins_loaded. Result: "Plugin could not be activated
because it triggered a fatal error" — class not found. The fix is those two extra
require_once lines above. Cheap lesson, now it's yours for free.The container exposes each component (tracker, data, display…) through a
get() accessor, so any part of the plugin can reach any other without globals scattered everywhere.
Chapter 2
Database schema: aggregate buckets, not raw hits
Post meta melts down at scale, and logging one row per pageview floods the table.
The middle path: one row per post / date / hour / referrer / device bucket,
incremented on each hit. A year of heavy traffic stays compact, and every report
is a simple GROUP BY.
CREATE TABLE wp_post_views (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
view_date DATE NOT NULL,
view_hour TINYINT(2) UNSIGNED NOT NULL DEFAULT 0,
count BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
unique_count BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
referrer_host VARCHAR(190) NOT NULL DEFAULT '',
device VARCHAR(20) NOT NULL DEFAULT '',
browser VARCHAR(40) NOT NULL DEFAULT '',
lang VARCHAR(20) NOT NULL DEFAULT '',
PRIMARY KEY (id),
KEY post_date (post_id, view_date)
);A second, smaller table stores reading-depth counters (25/50/75/100%). Every query in the
plugin goes through one data-access class using $wpdb->prepare() — the only
interpolated identifiers are column names from a fixed whitelist. SQL injection has no door to knock on.
Chapter 3
The tracking engine: count humans, skip robots, block nothing
The default counting mode is a JavaScript beacon that fires after the page renders —
using navigator.sendBeacon during idle time, so tracking adds zero blocking cost.
Cache plugins can't break it either, because the count happens client-side.
if ( 'requestIdleCallback' in window ) {
requestIdleCallback( function () { send( 'aipt_insight_track' ); } );
} else {
setTimeout( function () { send( 'aipt_insight_track' ); }, 300 );
}
// navigator.sendBeacon survives even if the user navigates away mid-requestServer-side, every hit passes a gauntlet before it counts: is it a preview or feed?
Is the post type tracked? Does the user-agent match ~25 bot signatures (filterable via
aipt_pva_bot_signatures)? Is the visitor logged in with an excluded role?
Uniqueness is decided by a capped cookie — or, in cookieless mode, a salted, anonymised
fingerprint stored as a transient that expires with the unique window. Devices and browsers
are classified from the UA, referrers reduced to their host so no query-string junk is stored.
Chapter 4
An admin UI people actually enjoy
Default WP settings tables work, but they don't feel like a product. Insight renders a
single-page admin app: brand bar with version pill, icon tabs (General · Display ·
Insights & Alerts · Privacy · Tools · Analytics), card sections with label/control rows —
and a dark/light switch that persists via localStorage, exactly like the rest of
the AIPromptTheory suite.
var app = document.getElementById( 'jptp-app' );
var saved = localStorage.getItem( 'jptp_theme' ) || 'dark';
app.setAttribute( 'data-theme', saved );
toggle.addEventListener( 'click', function () {
var next = app.getAttribute( 'data-theme' ) === 'light' ? 'dark' : 'light';
app.setAttribute( 'data-theme', next );
localStorage.setItem( 'jptp_theme', next );
} );Everything themes through CSS custom properties: a [data-theme="light"] selector
swaps about fifteen variables and the entire interface follows — the same trick this very
page inherits from the Ember theme you're reading it on.
Chapter 5
Charts, comparisons and the dashboard widget
Chart.js renders the views-over-time line (today / 7 / 30 / 90 / 365 days), the device and browser doughnuts, and the dashboard widget's per-day month bars. Range changes are AJAX: a nonce-protected endpoint returns a zero-filled daily series so gaps don't lie to you.
Period comparison is computed server-side — this week's total vs. last week's, with the delta badge going green or red. The widget gets month navigation (‹ May · June · July ›) that swaps both the chart and a ranked top-posts table without a reload. Every aggregate read is cached in the WP object cache and invalidated on write, so Redis/Memcached sites serve reports basically free.
Chapter 6
The novel ten: where it stops being a counter
Counting views is table stakes. These ten features are what make Insight worth a landing page:
- AI insights. A daily cron analyses 30 days of buckets and writes sentences a human would: "Your Tuesday traffic runs 40% above average — publish more on Tuesdays." Best weekday, week-over-week momentum, rising stars. Cached as a transient, filterable by devs.
- Heat-map calendar. 365 tiny squares, five intensity levels, pure CSS — your year at a glance, GitHub-style.
- Decay alerts. Any post whose recent average falls a configurable % below its historical peak gets flagged with a one-click "Refresh" link to the editor. Old gold gets polished instead of forgotten.
- Milestones. Crossing 100 / 1K / 10K views fires a (de-duplicated) admin email. Thresholds configurable.
- Reading depth. Scroll listeners report 25/50/75/100% marks — so you know if they read it, not just loaded it.
- Virality score. Shares-per-100-views, displayed beside the count when share data exists.
- Email digest. Weekly or monthly top-10 straight to your inbox, via WP-Cron.
- SVG badges. Every post gets a live badge URL — embed your view count anywhere on the internet.
- GDPR kit. IP anonymisation, fully cookieless mode, automatic retention purges, one-click delete-everything, and suggested privacy-policy text registered with WordPress core.
- Public leaderboard.
[aipt_leaderboard limit="10"]— a styled most-viewed list your visitors can see.
Chapter 7
Integrations: meet people where they build
A display feature nobody can place is a feature nobody uses. So the count renders six ways: auto-append to content, shortcode, sidebar widget, Gutenberg block (server-rendered, so counts are always live), an Elementor widget, and a Divi module — the last two registered conditionally so they only load when the builder exists.
GET /wp-json/aipt-insight/v1/views/123 → { "views": 4521 }
GET /wp-json/aipt-insight/v1/top?limit=10 → ranked posts (capability-gated)
POST /wp-json/aipt-insight/v1/track → count a view (headless front-ends)
wp aipt-insight views --post=42
wp aipt-insight top --limit=20
wp aipt-insight purge --days=365Theme developers get template tags (aipt_pva_get_views(), aipt_pva_the_views())
and an action/filter surface — aipt_pva_view_recorded, aipt_pva_milestone_reached,
aipt_pva_should_count — so the plugin is a platform, not a dead end.
Chapter 8
Shipping it — including the scars
Honest engineering content beats polished marketing, so here's what actually bit us:
require_once 'class-old-slug-*.php' strings while the files on disk kept their old
names. Fatal error on activation. Rule: when renaming a slug, rename the files and
the references in the same pass, then verify every require resolves before zipping.uninstall.php that drops tables is correct behaviour — until you tell someone
to "deactivate + delete + reupload" as an upgrade path and it deletes their analytics history.
Gate destructive cleanup behind an opt-in toggle, or document the FTP-swap path. We learned
this with our own data.The boring-but-vital checklist that survived: prepared statements everywhere, nonces +
capability checks on every action, escaped output, uninstall.php cleanup,
a .pot file for translators, multisite-aware activation, and semantic versioning
from day one.
Make it yours
Rebrand it. Seriously, we encourage it.
This is an educational project: take the code, rename it, ship it under your banner. Here is the exact rename map — change these consistently and nothing breaks (ask us how we know):
| What | Ours | Yours |
|---|---|---|
| Plugin folder & main file | aipt-insight/ | your-brand-insight/ |
| Plugin Name header | AIPromptTheory Insight | YourBrand Insight |
| Text domain | aipt-insight | your-brand-insight |
| Class file names | class-aipt-insight-*.php | rename files and requires together |
| REST namespace | aipt-insight/v1 | your-brand/v1 |
| DB tables / options | wp_post_views, aipt_pva_* | keep them — or migrate with RENAME TABLE |
Free, as in education
Get Insight + request the add-on pack
Insight is free. The add-on features are free too — we just build them in the order people ask for them. Tell us which one you want next and we'll email you when it ships.
Part of the AIPromptTheory suite: