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.

aipt-insight.php — the whole bootstrap
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 );
Scar #1 — the activation fatal. Our first build loaded every class inside 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.

dbDelta schema (views table)
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.

tracker.js — non-blocking by design
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-request

Server-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.

Theme switcher — 15 lines, no library
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:

  1. 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.
  2. Heat-map calendar. 365 tiny squares, five intensity levels, pure CSS — your year at a glance, GitHub-style.
  3. 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.
  4. Milestones. Crossing 100 / 1K / 10K views fires a (de-duplicated) admin email. Thresholds configurable.
  5. Reading depth. Scroll listeners report 25/50/75/100% marks — so you know if they read it, not just loaded it.
  6. Virality score. Shares-per-100-views, displayed beside the count when share data exists.
  7. Email digest. Weekly or monthly top-10 straight to your inbox, via WP-Cron.
  8. SVG badges. Every post gets a live badge URL — embed your view count anywhere on the internet.
  9. GDPR kit. IP anonymisation, fully cookieless mode, automatic retention purges, one-click delete-everything, and suggested privacy-policy text registered with WordPress core.
  10. 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.

REST + CLI for the headless crowd
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=365

Theme 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:

Scar #2 — the rename trap. When rebranding, we ran a find-and-replace on the old slug… which also rewrote the 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.
Scar #3 — the uninstall purge. A tidy 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):

WhatOursYours
Plugin folder & main fileaipt-insight/your-brand-insight/
Plugin Name headerAIPromptTheory InsightYourBrand Insight
Text domainaipt-insightyour-brand-insight
Class file namesclass-aipt-insight-*.phprename files and requires together
REST namespaceaipt-insight/v1your-brand/v1
DB tables / optionswp_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.

No spam, no upsells — you'll get the plugin zip and a ship-notification. That's it.