WP Manifestindependent plugin directory
manifest / admin / jb-siteaudit

JB SiteAudit releases

A WordPress audit log that records what changed, not merely that something did. Plugin agnostic, quiet by default, WCAG 2.2 AA.

by Jeremiah Beasley · github.com/jeremiahbeasley/jb-siteaudit · website

0stars
2release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/jeremiahbeasley/jb-siteaudit/releases/download/v1.2.1/jb-siteaudit-1.2.1.zip

Readme

JB SiteAudit

A WordPress audit log that records what changed, not merely that something did.

Most activity logs tell you a field was touched. This one tells you the field was blogdescription, that it held "Grouping test", and that it now holds something else — and it says so in a sentence a person can read.

  • Plugin agnostic by design. It hooks WordPress's own storage layer — post types, post meta, options, terms, users — so a plugin installed next year is covered the day it arrives, with no code written for it.
  • Quiet. Machinery that WordPress and plugins write about themselves is filtered out at source. On one real site, 22 of 28 recorded rows were noise; they are no longer recorded at all.
  • Accessible. WCAG 2.2 Level A and AA, including reflow at 320px, verified with a real browser driving real key presses.

Requires WordPress 6.0+ and PHP 7.4+.


What it records

Area Examples
Content Any post type: published, edited, trashed, restored, deleted. Title, body, web address and parent changes, each with before and after
Custom fields Added, changed and removed — including the value a removed field held
Settings Any option, with the previous and new value
Users Created (with role and email address), role changes, profile edits, deletion, sign-in and sign-out
Categories and tags Created, renamed, re-described, re-parented, deleted
Media Uploaded and deleted
Plugins and themes Activated, deactivated, switched, and updated — with the version before and after
Plugin data tables Changes inside a plugin's own tables, attributed to the plugin that owns them

What it deliberately does not record

This is an audit log of decisions people make, not a copy of your database.

  • Submitted records — form entries, orders, donations, subscribers. Those are what the public sends in, not what a signed-in person changed.
  • Visitor analytics — view counters, hit tables, crawl results.
  • Credentials. Any option or field whose name suggests a password, token, API key, secret, salt or licence is recorded as (value not shown). The detail screen withholds it too — it must never become the place a secret is printed.
  • Machine bookkeeping — transients, locks, cached version stamps, dismissed notices, install dates, and the settings a plugin rewrites during its own update.

The four screens

Activity Log — the entries, most recent first. Knock-on changes are nested under the action that caused them, so one editor save that writes forty custom fields is one row you can expand, not forty rows.

Entry detail — the complete before and after values (the list truncates at 90 characters), the exact setting or field name, checksums for content too large to store in full, where the change was made from, and a link to open the changed item.

Coverage — computed live for this site: for every active plugin, does it register post types, own tables, write options, have an adapter — and therefore is it fully covered, settings-only, watched by fingerprint, or not covered. Installing a plugin updates this with no code change.

Settings — retention, size caps, ignore lists, and the table watcher switch.


Installation

Download the release zip and install it through Plugins → Add New → Upload, or clone into wp-content/plugins/:

git clone https://github.com/jeremiahbeasley/jb-siteaudit.git

Activate it. The log table is created on activation and the menu appears as JB SiteAudit. Access requires the jbsa_view_log capability, granted to administrators on activation.


Settings

Setting Default What it does
retention_days 90 How long entries are kept. 0 keeps everything
max_detail_bytes 20000 Largest single value stored in full
watch_tables on Watch plugin-owned tables for changes
checksum_max_bytes 52428800 Above this a table is tracked by row count only, because a checksum would read too much of it to be affordable on a request
ignored_option_keys Settings never recorded
ignored_meta_keys Custom fields never recorded
ignored_post_types Content types never recorded
ignored_tables Data tables never watched
client_ip_header Empty trusts REMOTE_ADDR. Set to a server key such as HTTP_X_FORWARDED_FOR only when a trusted proxy sets it

For developers

Record your own events

Any plugin can write to the log without depending on this one. If JB SiteAudit is not installed the action simply goes nowhere.

do_action( 'jbsa_log', array(
    'event_type'  => 'entry_checked_in',
    'object_type' => 'forminator_entry',
    'object_id'   => 42,
    'object_name' => 'Jane Doe',
    'summary'     => 'Checked in at the desk',
) );

Filters

Filter Purpose
jbsa_ignored_post_types Content types to skip entirely
jbsa_ignored_meta_keys Custom fields to skip entirely
jbsa_ignored_option_keys Settings to skip entirely
jbsa_skip_option Decide per option name — receives ( bool $skip, string $name )
jbsa_classify_table Classify a table as core, machine or watch — receives ( string $class, string $name )

Example — stop recording a noisy option your plugin writes on every page load:

add_filter( 'jbsa_skip_option', function ( $skip, $name ) {
    return $skip || 'myplugin_last_ping' === $name;
}, 10, 2 );

Why a table watcher exists

Roughly three quarters of the most-used WordPress plugins store their settings in post types or options, so hooking core storage covers them for free. The rest own their tables, and no core hook can see a plugin write to its own table.

That gap cannot be closed by a shipped list of supported plugins, for a reason that was measured rather than assumed: Forminator, Code Snippets, Redirection and Elementor create no tables at installation and create them lazily on first use. Any precomputed compatibility list understates reality the moment a feature is used.

So the watcher discovers non-core tables at runtime from the site's own prefix, classifies them, attributes them to the owning plugin by searching active plugin code for the table suffix, and fingerprints them. Where a table has a column a person would recognise, changes are named — "added logo_url", "edited colour". Where it has only numeric row ids, the entry says how many records moved and states plainly that contents were not recorded, rather than printing row numbers and pretending that is information.

See docs/COVERAGE.md for the measured results.


Accessibility

The interface targets WCAG 2.2 Level A and AA, and is tested rather than asserted. A Playwright suite signs in to a real WordPress install and drives the interface with real key presses:

  • The entry list is an ARIA treegrid with roving tabindex — one tab stop for the whole grid, Up and Down between rows, Left and Right within a row, and Left and Right on the first column to collapse, expand and move to the parent.
  • Reflow at 320px on all four screens, with no horizontal page scrolling. Wide tables scroll inside their own container.
  • Status is never carried by colour alone.
  • Focus is visible and never obscured.

Automated scanners are not treated as sufficient: they check attributes and cannot catch an interaction failure.


Privacy and data protection

The log stores the acting user's login and ID, their IP address, and a description of what changed. Because it can hold personal data:

  • Set retention_days to match your retention policy. The default is 90 days.
  • Where a proxy sits in front of the site, set client_ip_header so the recorded address is the real client and not the proxy.
  • Values that look like credentials are never stored, only noted as withheld.
  • Client-supplied values, including the user agent, are sanitised before they are stored.

Coding standards

Checked with the official Plugin Check tool — the same one WordPress.org runs on submission — at zero errors.

The warnings that remain are direct database access, which is inherent to a plugin that owns a table and reads it live. Each significant case carries a written justification in the source: table names come from $wpdb->prefix, sort columns are whitelisted, every user value is bound as a placeholder, and results are deliberately not cached, because a stale audit log would misrepresent the record.

See docs/DEVELOPERS.md if you intend to contribute.


Licence

GPL-2.0-or-later. See LICENSE.

Copyright © Jeremiah Beasley.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.2.1 Aug 16, 2026 jb-siteaudit-1.2.1.zip 2
v1.2.0 Aug 16, 2026 jb-siteaudit-1.2.0.zip 0