Title: Semantic Journey
Author: hdfraser
Published: <strong>July 17, 2026</strong>
Last modified: July 24, 2026

---

Search plugins

![](https://ps.w.org/semantic-journey/assets/banner-772x250.png?rev=3621870)

![](https://ps.w.org/semantic-journey/assets/icon-256x256.png?rev=3621870)

# Semantic Journey

 By [hdfraser](https://profiles.wordpress.org/hdfraser/)

[Download](https://downloads.wordpress.org/plugin/semantic-journey.zip)

 * [Details](https://ewe.wordpress.org/plugins/semantic-journey/#description)
 * [Reviews](https://ewe.wordpress.org/plugins/semantic-journey/#reviews)
 *  [Installation](https://ewe.wordpress.org/plugins/semantic-journey/#installation)
 * [Development](https://ewe.wordpress.org/plugins/semantic-journey/#developers)

 [Support](https://wordpress.org/support/plugin/semantic-journey/)

## Description

Semantic Journey answers questions about your content that conventional analytics
answers badly or not at all: what does this content experience look like for real
readers, and did they reach the actions that matter to you? Not how many events 
fired, how long sessions lasted, or how you compare to a benchmark.

It is a plain-language, privacy-first alternative to conventional analytics for 
editorial teams, built around a fixed contract:

 1. **Semantic, not mechanical.** “Readers reached the end”, never “scroll depth exceeded
    80%”.
 2. **Understandable without training.** No analytics vocabulary, no dashboards that
    require interpretation skills.
 3. **Outcome-first language.** Progress, completion, confidence, clarity, friction.
    Never metrics, events, rates, or funnels.
 4. **Privacy-first by design.** No personal data, no identifiers, no cookies, no user
    tracking, nothing sent to third parties.
 5. **Aggregate-only signals.** No sessions, no individuals, no cross-page journeys,
    no behavioral profiling.
 6. **Liftable by default.** Every module is self-contained and works independently
    of any other plugin.

Semantic Journey works on any public post type, not only posts and pages: products,
portfolio items, or any custom post type your site uses.

#### Reader Progress (module 1)

The reference implementation of the contract above. Reader Progress reports four
fixed milestones for each piece of content:

 * **Started Reading.** The reader began engaging with the main content.
 * **Reached the Middle.** The reader progressed through roughly half of the content.
 * **Read Most.** The reader consumed the large majority of the content (more forgiving
   than strict completion).
 * **Reached the End.** The reader reached the conclusion.

Detection is content-relative, using IntersectionObserver against the primary content
container, never scroll events, scroll percentages, time-on-page, or mouse movement.
Milestones fire at most once per page view and are recorded as counts only: no per-
event timestamps, no session or user identity of any kind.

#### Decision Point (module 2)

A Decision Point marks the moment a reader reached a site-owner-defined action: 
a “Buy Now” button, a survey link, a phone number. Unlike Reader Progress’s four
fixed milestones, Decision Point labels are yours to define, and a Decision Point
can be reached more than once per page view since each click is a genuine, separate
signal.

Four ways to mark a Decision Point, in order of precedence:

 * A `data-sj-decision="Label"` attribute added to any element, for precise control
   from your theme or page builder.
 * Phone number links (`tel:`), email links (`mailto:`), and text message links (`
   sms:`), all detected automatically with no configuration.
 * An admin-defined CSS selector rule (Settings > Decision Point), matching any 
   element, not only links. This is the option for AJAX-submitted forms, Add to 
   Cart buttons, or anything a page builder generates without a real link underneath
   it.
 * An admin-defined URL pattern rule (Settings > Decision Point), for marking real
   links by address without touching a template.

Decision Point data appears as additional columns on the same Reports screen as 
Reader Progress, and in the same CSV export, built dynamically from whichever labels
actually have data in the selected time window.

#### Reports

A dedicated Reports screen shows, for any content item, how many readers started,
reached the middle, read most, or reached the end, over a 7/30/90-day window, plus
a column for each Decision Point label with data in that window, with search, column
sorting, and a CSV export. The four Reader Progress columns use plain-language milestone
names and show counts only, no rates, percentages, or scores.

An At a Glance summary above the table shows the same five counts site-wide, each
with a percentage: a share of Started Reading, computed from data already collected.
This plugin does not track total pageviews or site visitors, and did not add that
tracking to produce these percentages; Started Reading and Decision Point clicks
intentionally have no percentage of their own, since neither is meaningfully a share
of Started Reading. Below a configurable minimum sample (Settings > Reporting, default
30), only the count is shown, since a percentage computed from a handful of readers
risks overstating what that small a sample actually shows.

#### Copy prompt for your LLM

The Reports screen can build a plain-text summary of exactly what is currently displayed(
the full filtered set, not just the current page) for you to paste into an LLM of
your choice, if you want a plain-English interpretation of the counts. This plugin
does not send this text anywhere itself, there is no outbound link and no third-
party integration; it copies to your clipboard only, and where you paste it afterward
is entirely your own choice. The built-in instructions ask the LLM not to invent
rates or percentages and not to assert a definite cause for any pattern, matching
this plugin’s own approach to the same data.

#### On the post editor

A small sidebar box on the post/page edit screen (and any other trackable post type)
confirms whether Reader Progress and Decision Point are recording for that piece
of content, showing 30-day counts for each, with a link straight to the filtered
Reports screen. Read-only, no fields to fill in.

#### On the Posts and Pages list, and the Dashboard

A Semantic Journey column on the Posts/Pages list table (and any other trackable
post type) gives an at-a-glance summary without opening each item. A Dashboard widget
shows the same five At a Glance counts as Reports, over the last 7 days, plus the
top 3 pieces of content by readers reaching the end.

#### Privacy and external services

This plugin does not send any data to third-party services. All data collected by
Reader Progress stays in your own WordPress database, in a dedicated table, as aggregate
daily counts. Nothing is shared externally, and no cookies or personal identifiers
are used at any point.

#### For developers

Other plugins can integrate with Semantic Journey through a small, stable public
API (see includes/functions.php):

 * `semantic_journey_is_module_enabled( string $module, $context = null ): bool`
 * `semantic_journey_reader_progress_get_summary( int $content_id, int $days = 30):
   array`
 * `semantic_journey_decision_point_get_summary( int $content_id, int $days = 30):
   array`
 * the `semantic_journey_module_enabled` filter, for applying your own licensing
   or per-post rules on top of the site-level toggle.
 * the `semantic_journey_module_data_recorded` action, fired after every successful
   Reader Progress and Decision Point write with the module slug, content ID, date,
   and that day’s full current row.
 * the `semantic_journey_reports_screen_tabs` action, fired once on the Reports 
   screen with the currently selected date window (start date, end date), for rendering
   an additional tab.

Semantic Journey itself has no concept of licensing, plans, or host-specific gating;
that interpretation belongs entirely to the integrating plugin.

## Screenshots

[⌊Reader Progress report: plain-language milestone counts per content item, with
a date-range filter, search, and CSV export.⌉⌊Reader Progress report: plain-language
milestone counts per content item, with a date-range filter, search, and CSV export
.⌉[

Reader Progress report: plain-language milestone counts per content item, with a
date-range filter, search, and CSV export.

[⌊Settings screen: module toggles, retention window, and uninstall data-removal 
preference.⌉⌊Settings screen: module toggles, retention window, and uninstall data-
removal preference.⌉[

Settings screen: module toggles, retention window, and uninstall data-removal preference.

## Installation

 1. Upload the `semantic-journey` folder to `/wp-content/plugins/`, or install it via
    Plugins > Add New > Upload Plugin.
 2. Activate Semantic Journey through the Plugins menu. Activation creates the `wp_reader_progress_daily`
    and `wp_decision_points_daily` tables and schedules their daily retention-purge
    events.
 3. Go to Semantic Journey > Settings to confirm Reader Progress and Decision Point
    are enabled, set your retention windows, and optionally add Decision Point URL 
    pattern rules.
 4. Visit Semantic Journey > Reports to see reader activity once readers have visited
    your content. No further configuration is required; Reader Progress and automatic
    tel:/mailto:/sms: link detection both work immediately after activation.

## FAQ

### Why did a reader’s milestone or click not get recorded?

The most common reason is that JavaScript or a required browser feature was unavailable,
described elsewhere in this FAQ. One other case: each submission carries a short-
lived security token, similar to how WordPress protects its own forms, which identifies
nothing about the visitor but expires roughly a day after the page loaded. If a 
browser tab is left open far longer than that before a reader reaches a milestone,
that one signal is silently not recorded rather than recorded against an expired
token. This is intentional: a missing signal in that rare case is preferable to 
weakening the same check for every other visitor.

### Does the Copy prompt for your LLM feature send my data anywhere?

No. It builds a plain-text summary and copies it to your clipboard only. There is
no outbound link, no API call, and no third-party integration built into this feature.
Whether you paste that text into an LLM afterward, and which one, is entirely your
own separate choice.

### Does this replace Google Analytics or a similar tool?

No. It answers a narrower, editorial question and is not a general-purpose analytics
platform. It has no sessions, no user identity, and no cross-page journeys by design.

### Does Reader Progress use cookies?

No. It stores no identifiers of any kind, and no data is shared with any third party.

### What happens if a reader has JavaScript or IntersectionObserver disabled?

The module silently disables itself for that page view. No partial or misleading
data is ever recorded.

### How long is data kept?

90 days by default, on a rolling basis, configurable in Semantic Journey > Settings.
Older rows are purged automatically once a day.

### What happens to my data if I uninstall the plugin?

By default, nothing is removed, in case you are reinstalling or upgrading. If you
want your data removed on uninstall, enable “Remove data on uninstall” in Settings
first.

### Can a Decision Point be reached more than once by the same visitor?

Yes. A Decision Point is a click on an action, not a reading milestone, so each 
click is counted, matching how a conversion or goal click is normally understood.
This is a deliberate difference from Reader Progress’s four milestones, which each
fire at most once per page view.

### Why isn’t my button being recorded as a Decision Point?

URL pattern rules only match real links (an element with an href). Many buttons,
especially ones generated by page builders or ones that submit a form via AJAX, 
are not links at all. For those, use a CSS selector rule instead (Settings > Decision
Point), which matches any element, or add a `data-sj-decision="Label"` attribute
directly to the button. If you configured a CSS selector rule and it still is not
firing, check your browser’s console: an invalid selector is reported there rather
than failing with no explanation at all. If the element is a phone number, email,
or text message link, no rule is needed at all, those three are detected automatically.

### Does this work on a WordPress multisite network?

Yes. Activating network-wide sets up every existing site, and any site created afterward
on a network-active install is set up automatically. Each site’s data stays separate;
there is no cross-site rollup in the free version.

### Does this affect my privacy policy?

Semantic Journey adds a short, accurate entry to Tools > Privacy > Policy Guide 
describing what it does: it does not collect personal data of any kind. You can 
include this text in your site’s privacy policy as-is or edit it to match your own
wording.

### I thought this plugin only showed counts. Why do I see percentages on At a Glance?

The At a Glance summary (Reports and the Dashboard widget) shows a percentage alongside
each count, a deliberate, documented exception to how the rest of this plugin works.
Each percentage is a share of Started Reading, computed from data already collected;
this plugin still does not track total pageviews or site visitors, and did not add
that tracking to produce these numbers. Started Reading and Decision Point clicks
intentionally have no percentage of their own. Below a configurable minimum sample(
Settings > Reporting, default 30), only the count is shown, no percentage at all,
since a rate computed from a handful of readers risks overstating what that small
a sample actually shows. The Reports table itself, and the CSV export, remain counts
only.

### What is the “What this suggests” line on the Individual insight example?

It is generated by a plain, deterministic comparison of that one content item’s 
own milestone counts (which stage had the largest drop, or whether a Decision Point
is getting few clicks relative to readers who finished), not by an AI model and 
not sent anywhere to generate. It is phrased cautiously (“may suggest,” “worth checking”)
rather than as a confident claim, since a comparison of a few numbers cannot know
the actual cause of a pattern. For a fuller, plain-English read on a piece of content,
use Copy prompt for your LLM instead.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Semantic Journey” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ hdfraser ](https://profiles.wordpress.org/hdfraser/)

[Translate “Semantic Journey” into your language.](https://translate.wordpress.org/projects/wp-plugins/semantic-journey)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/semantic-journey/),
check out the [SVN repository](https://plugins.svn.wordpress.org/semantic-journey/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/semantic-journey/)
by [RSS](https://plugins.trac.wordpress.org/log/semantic-journey/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 2.2.3

 * Fixed the same two Plugin Check warnings correctly this time: two separate suppression
   comments are needed, one per line, since different sniffs within the same statement
   fire on different physical lines. The 2.2.2 fix moved one comment and inadvertently
   stopped covering three others.

#### 2.2.2

 * The 2.2.1 fix for two Plugin Check warnings was placed on the wrong line (phpcs:
   ignore only suppresses the very next line, and the comment sat several lines 
   above the actual SQL string). Moved both comments to the correct location.

#### 2.2.1

 * Fixed three missing translators comments on At a Glance’s detail sentences, flagged
   by Plugin Check.
 * Corrected a phpcs:ignore comment on the two get_row_for_date() methods to cover
   the specific rule that actually fired (WordPress.DB.PreparedSQL.InterpolatedNotPrepared),
   not just an adjacent one.

#### 2.2.0

 * Added two extension points for third-party integrations: semantic_journey_module_data_recorded(
   fired after every Reader Progress and Decision Point write) and semantic_journey_reports_screen_tabs(
   fired on the Reports screen with the active date window). Both are no-ops until
   something listens.

#### 2.1.0

 * Added a Low-Traffic Display Policy: At a Glance percentages now require a minimum
   sample size before appearing (Settings > Reporting, default 30, configurable),
   below it only the count is shown. Found by comparing notes with Semantic Journey
   Static, which had already built this independently.

#### 2.0.4

 * Added a “View details” link to each At a Glance tile on Reports, sorting the 
   table below by that tile’s matching column and jumping to it.

#### 2.0.3

 * Fixed the header logo rendering at 22px instead of the intended 36px, a leftover
   CSS rule was overriding the markup’s own sizing.
 * Added a tagline to the header, matching the one already on the footer.
 * Reports’ At a Glance tiles now include a short descriptive sentence for each,
   in addition to the count. The Dashboard widget’s compact tiles are unchanged.

#### 2.0.2

 * Reports’ header reverted to the flat, light treatment (white background, bold
   dark text), replacing the dark purple bar added in 2.0.0. The badge now uses 
   the complete purple-disc logo directly.

#### 2.0.1

 * Fixed the About Semantic Journey link, which pointed at a page that does not 
   exist; now points at the verified base domain.
 * The day-range selector now sits at the top of Reports, next to At a Glance, which
   now also states its active window in words. Previously the control was buried
   below, making the same tiles on the Dashboard widget and Reports look like an
   unexplained duplicate of each other.
 * The shared feature footer now includes the Semantic Journey logo, name, and tagline;
   previously it was four unattributed cards.

#### 2.0.0

 * Percentages added to At a Glance (Reports and the Dashboard widget), a deliberate,
   documented exception to this plugin’s usual counts-only rule. Each is a share
   of Started Reading; no new tracking (pageviews, visitors) was added to produce
   them. The Reports table and CSV export remain counts only.
 * A “What this suggests” line added to the Individual insight example, generated
   by a deterministic comparison of one content item’s own counts, not by AI, phrased
   cautiously rather than as a confident claim.
 * Dashboard widget rebuilt: the same five At a Glance tiles as Reports, plus a 
   trimmed 3-item content list below.
 * Reports header reverted to a dark purple bar with a white mark, matching the 
   original design concept. Settings keeps its flat white header.
 * Added a “What do these insights mean?” card to Reports, linking to semanticjourney.
   com.
 * Individual insight example now also shows Decision Point clicks for the same 
   content item, not only the four Reader Progress milestones.
 * Settings restructured into a sidebar-navigated layout (General, Reader Progress,
   Decision Points, Privacy & Data).

#### 1.9.0

 * Settings now uses the same bold-heading, gray-subtext style as Reports, instead
   of plain section headings.
 * Added a shared feature footer (Privacy First, WordPress Native, Editor Focused,
   Yours Alone) to both Reports and Settings.
 * Added an “Individual insight example” panel to Reports, showing one real piece
   of content’s four milestone counts as bars. Counts only, no percentages, no narrative
   commentary.

#### 1.8.1

 * At a Glance tiles now use five distinct icons instead of one dot recolored five
   times.
 * Fixed the Reports toolbar layout drifting apart (day-range tabs, Export CSV, 
   and search box).
 * Fixed a bug where searching Reports silently reset the day-range filter back 
   to 30 days.

#### 1.8.0

 * New brand direction: a purple/pastel palette and a flat, white admin header, 
   replacing the earlier navy/cream full-width bar.
 * Added an “At a glance” summary strip to Reports: five real counts (the same milestone
   names used everywhere else in this plugin, plus Decision Point clicks), site-
   wide across the current filter.
 * Added a read-only rules preview table to Settings > Decision Point, showing every
   configured URL pattern and CSS selector rule together in one table.

#### 1.7.1

 * Email links (mailto:) and text message links (sms:) now join phone number links
   at the automatic, zero-configuration Decision Point detection tier.

#### 1.7.0

 * Added CSS selector rules to Decision Point (Settings > Decision Point): a fourth
   way to mark a Decision Point that works on any element, not only links. Closes
   a real gap for AJAX-submitted forms, Add to Cart buttons, and anything a page
   builder generates without a real href underneath it.
 * Invalid CSS selector rules are now reported in the browser console rather than
   failing silently.

#### 1.6.4

 * Replaced an inline closure used to sanitize a settings checkbox with WordPress’s
   own rest_sanitize_boolean(), also fixing an edge case where the literal string“
   false” would have been treated as enabled.

#### 1.6.3

 * Fixed a CSV formula injection risk (CWE-1236) in the Reports CSV export: post
   titles and Decision Point labels are now protected against being interpreted 
   as spreadsheet formulas when the exported file is opened. Found proactively, 
   not from an external report.
 * Added a UTF-8 BOM to the CSV export so non-ASCII characters display correctly
   in Excel.

#### 1.6.2

 * Both REST endpoints (Reader Progress, Decision Point) now require a nonce, verified
   server-side, in addition to remaining reachable by anonymous visitors as intended.
   This closes a gap where either endpoint could previously be scripted against 
   directly with no prior reconnaissance at all. Identifies nothing about the visitor
   and requires no cookie or login state.
 * Fixed a sanitization gap: a setting’s array keys were only cast to strings rather
   than sanitized.
 * Corrected the Contributors field in this readme.

#### 1.6.1

 * Fixed an escaping issue in the Posts list column flagged by Plugin Check: output
   is now escaped once, correctly, at the actual point of output.
 * Documented one read-only display-filter nonce warning introduced by the Copy 
   prompt feature’s search handling.

#### 1.6.0

 * Added a branded admin page header shared across Reports and Settings.
 * Added “Copy prompt for your LLM” on Reports: builds a plain-text summary of the
   currently filtered report for you to paste into an LLM of your choice. Copy only;
   this plugin sends nothing anywhere itself.
 * Accessibility fixes found in a dedicated sweep: corrected two color combinations
   that measured below WCAG AA contrast thresholds, fixed a hidden field that was
   reachable by keyboard despite being visually and semantically hidden, added heading
   semantics and aria-current where state was previously conveyed by CSS class alone,
   and fixed a redundant image description that caused a double announcement.
 * Updated Plugin URI and Support URI to semanticjourney.com.

#### 1.5.1

 * Fixed a bug where any module toggle not already known to the Settings screen 
   was silently discarded on every write to the module-enabled setting, not just
   from the Settings form itself. This could have prevented a future module’s toggle
   from ever persisting.

#### 1.5.0

 * Fixed a scope mismatch: Reports and CSV export now cover every trackable post
   type (matching what the front-end script actually tracks), not just posts and
   pages. Sites with custom post types were having activity recorded but not shown.
 * The editor box now shows Decision Point alongside Reader Progress.
 * New Semantic Journey column on the Posts/Pages list table (and any other trackable
   post type).
 * New Dashboard widget: top 5 content items by readers reaching the end, last 7
   days.
 * Multisite: network-wide activation now sets up every site; a site created later
   on a network-active install is set up automatically.
 * Added a privacy policy content declaration under Tools > Privacy > Policy Guide.

#### 1.4.1

 * Addressed the first WordPress.org Plugin Check pass: documented justifications
   for read-only display-filter nonce warnings, the CSV export’s file-stream handling,
   and direct-query caching warnings, and removed an unused Domain Path header. 
   No functional change.

#### 1.4.0

 * Added Decision Point, module 2 of Semantic Journey: site-owner-defined action
   clicks such as a Buy Now button, a survey link, or a phone number, marked via
   a data-sj-decision attribute, automatic tel: link detection, or an admin-defined
   URL pattern rule.
 * Decision Point data appears as additional columns on the existing Reports screen
   and in CSV export, alongside the four Reader Progress milestones.
 * The Reports screen’s underlying query was restructured so a post with only Decision
   Point activity and no Reader Progress data still appears; it would previously
   have been hidden.

#### 1.3.0

 * Added an editor sidebar meta box (Reader Progress) on the post/page edit screen,
   showing whether Reader Progress is recording for the current post and its 30-
   day milestone counts, with a link to the filtered Reports screen. Read-only; 
   no fields, no derived score.

#### 1.2.0

 * Internal only, no user-facing change: retrofitted to the HDUK WordPress.org Plugin
   Development Standards v1.0. Classes renamed for prefix-length compliance, database
   layer consolidated into a single class with a private table getter, every direct
   database query documented with a phpcs:ignore justification, and the semantic_journey_module_enabled
   filter renamed from its earlier slash-namespaced form.
 * Table name (wp_reader_progress_daily) is unchanged; data and settings from 1.1.0
   carry over automatically.

#### 1.1.0

 * Added the Reports screen (Semantic Journey > Reports): a standard, sortable, 
   searchable admin table of Reader Progress milestone counts per content item, 
   with 7/30/90-day filters and CSV export.
 * Reader Progress write path rewritten to use fully static, per-branch SQL for 
   each milestone, so no column name is ever interpolated into a query.
 * Menu restructured: Reports and Settings now live under a single top-level Semantic
   Journey menu.

#### 1.0.0

 * Initial standalone release. Semantic Journey extracted from the CQIP plugin into
   its own framework, with no dependency on any other plugin.
 * Reader Progress rebuilt on a dedicated wp_reader_progress_daily table (previously
   post meta), with atomic increment writes and a configurable retention-purge cron.
 * New generic semantic_journey_module_enabled filter allows any host product to
   integrate without a hard dependency.
 * New Settings screen for module toggles, retention window, and uninstall data-
   removal preference.

## Meta

 *  Version **2.2.3**
 *  Last updated **2 weeks ago**
 *  Active installations **10+**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.0.2**
 *  PHP version ** 7.4 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/semantic-journey/)
 * Tags
 * [content quality](https://ewe.wordpress.org/plugins/tags/content-quality/)[privacy](https://ewe.wordpress.org/plugins/tags/privacy/)
 *  [Advanced View](https://ewe.wordpress.org/plugins/semantic-journey/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/semantic-journey/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/semantic-journey/reviews/)

## Contributors

 *   [ hdfraser ](https://profiles.wordpress.org/hdfraser/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/semantic-journey/)