All plugins

Dragon Activity Log

A comprehensive, tamper-evident activity log for WordPress. See who changed what, when, and from where — with field-level change detail.

A tamper-evident audit log: every entry is cryptographically chained to the one before it, so silent edits to history are detectable, not just "mirrored somewhere else".

Requirements

WordPress 6.2+, PHP 8.0+. Multisite: network activation creates a table per site, new sites are provisioned automatically, and uninstall (with the opt-in) runs on every site. Every screen, email and notice is translatable; community translations come from translate.wordpress.org.

What gets logged

Content changes (with field-level detail of what changed), user logins, logouts, failed logins and profile changes, role changes (on multisite also removal from a site and super admin grants), plugin, theme and core updates (a failed single update is recorded as failed), settings changes (including the plugin's own), media including alt text edits, taxonomy terms and comments. Logins are attributed to the user signing in. Clearing the log itself records a "log cleared" event, and exports, rollbacks and failed alert deliveries are logged too. While WooCommerce is active: new orders, order status changes, product stock quantity and stock status changes, and new coupons (on stores that keep orders in the posts table, orders are not logged a second time as posts; a new coupon is logged once as a coupon and its later edits as post events). Store events recorded with no logged-in user (a guest checkout, a payment gateway callback or a scheduled task) keep no IP address. Dragon plugins (Redirect Manager and others) feed their own events in automatically when active.

Reading the log

Tools > Activity Log opens the log. Filter by object type, event, severity and date, or search; the live-updates toggle streams new events onto the first page and pauses itself while a filter or search is active. Screen Options (top right) sets events per page and hides columns. Times follow your site's date and time format. Field-level change detail shows exactly what changed, not just "post updated". Severity is shown as a tinted pill (info, notice, warning, critical) with a stripe on warning and critical rows.

Full details and rollback

Post and setting events, and role changes, have a Full details link under the date. For a post it shows a side-by-side diff of the whole content plus any change to the title, excerpt or status; for a setting it shows the whole value before and after (arrays as formatted JSON). The copies behind the diff live in a separate wp_dal_snapshots table, are taken at the moment of the change (from version 1.1.0 on), and are deleted with their event by retention cleanup, Clear log or a privacy erasure. A side larger than 256 KB is not kept, and the diff says "(not captured)" for it; a setting that did not exist before the change, or was deleted by it, shows "(did not exist)" or "(deleted)" on that side. On multisite, the full value of a setting is shown only to super admins.

From the same dialog a setting change or a role change can be rolled back. A setting goes back to its previous value; a role change is undone exactly (the roles it took away are given back, the roles it gave are taken away). The rollback is refused, with the reason, when the setting or the user's roles changed again since the event, when it was already rolled back, when the previous value contains a hidden secret or data that cannot be stored exactly, when you may not give that role, or when it would remove your own permission to change roles. A change that created a setting is not rolled back (that would delete it), and neither is a change that deleted one (a deleted setting is not re-created). After the permalink structure, category base or tag base is restored, the stored permalink rules are emptied so WordPress rebuilds them on the next page load. When the restore leaves plain permalinks, the dialog asks you to save Settings > Permalinks once, because only that screen writes the server's rewrite rules (.htaccess). Restoring a setting needs Manage options (on multisite, the super admin's Manage network options); undoing a role change also needs permission to change that user's role. Every rollback is written into the chained log as "Change rolled back", naming the event it undid, even when your role is excluded from logging. Post edits and trashed posts are not rolled back here; WordPress's revisions and Trash already do that.

Export

The log tab has CSV and JSON export buttons. They export the events that match the filters currently applied (or the whole log), newest first. The CSV has translated headings, severity and role names, and a change-detail column; any cell that would start a spreadsheet formula (=, +, -, @) is prefixed with a quote. The JSON holds the rows as stored, hashes included. Each export is logged with its format, row count and filters.

Security alerts

Under Settings > Security alerts, switch on email alerts (off by default) and choose which: a login from an IP address the user has not used before, a burst of failed logins from one address, and a user made administrator (or super admin on a network). New-address alerts compare the address the log records against the last 20 the user logged in from on this site, so they follow the IP capture and anonymization settings; a user's first login only starts the list. A burst alert is sent when an address reaches the threshold (5 failed logins by default) inside the window (5 minutes by default, counted from the first failure); an IPv6 address counts together with its /64 network, and an IPv4-mapped IPv6 address as its IPv4 address. At most one burst alert is sent per window for the whole site: addresses that reach the threshold in the meantime are listed in the next burst alert (up to 10, plus a count of the rest). To count the attempts the connecting address is kept as a hashed key that expires with the window, even with IP capture off, and the alert and the held list show addresses only when capture is on. Alerts go to the site admin email, or to up to 10 addresses you list one per line; a field containing markup or anything that is not an address is refused whole and the previous list kept. Mail is sent at the end of the request; a failed delivery is recorded in the log at most once an hour.

The hash chain

Each event is sealed into a chain where every entry carries a hash of its predecessor (SHA-256 over an unambiguous encoding of every stored column, including the event id). The Integrity tab verifies the whole chain and reports the first link that no longer matches if anything was altered or deleted directly in the database. Sealing runs asynchronously (hourly, plus an occasional end-of-request pass) under a database advisory lock (with an option-row fallback on hosts without GET_LOCK) so logging adds no meaningful overhead to requests; "Awaiting seal" counts events written since the last seal, and verification seals them first, then reports anything still unsealed, tells you if it ran out of time on a very large log, and refuses to call the chain intact when another process held the lock. Retention cleanup verifies the events it is about to remove before deleting them, never removes events that are not sealed yet, and records every cleanup that removes events as a "log.pruned" event. If the chain is broken, cleanup pauses (Site Health and the Settings tab say so) until the break is resolved; Clear log rebuilds the chain. Two limits, by design: a row deleted before it is sealed leaves no trace, and someone with database write access who also recomputes every later hash and the stored chain head can hide an edit. It is tamper evidence for ordinary edits and deletions, not a substitute for database access control or off-site backups.

Off-site anchor

The chain lives in the same database as the log, so someone with database access could rewrite both consistently. An anchor is a copy of the chain head (event number + hash, never event data) sent out of the database: by email on a daily or weekly schedule (Settings > Chain anchor; use an address outside the site), and optionally by an HTTPS POST to a webhook you control, signed with a per-site secret in the X-Dragon-Signature header (sha256 HMAC of the JSON body). Send anchor now on the Integrity tab takes one immediately; Compare with an anchor (or wp dragon-activity-log verify --anchor=<reference>) recomputes the chain up to the anchored event and confirms it still produces the anchored hash. Every anchor sent is itself recorded as an event. When the anchored events were removed by retention cleanup, Clear log or a privacy erasure, the check reports that the anchor cannot be checked and names the event that recorded the removal and its date, so you can confirm it was yours; it is never shown as a match. WP-CLI exits 0 for a match, 2 for an anchor that cannot be checked, and 1 for everything else (mismatch, broken chain, busy or unreadable log). The webhook must use https, redirects are not followed, and the signing secret is stored encrypted.

Site Health

Tools > Site Health shows an "Activity log integrity" test (green when the chain verifies, red naming the first broken event, amber when the check needs more time or the log is busy) and an "off-site anchor" test (recommends turning anchoring on, or flags a missing/failed recent delivery). The Info tab has a Dragon Activity Log section with event counts, the sealed cursor, the chain fingerprint and anchor status.

Settings

The Settings tab holds retention (in days; 0 keeps events forever, and Run cleanup now applies the number in the field immediately, saved or not), IP capture and anonymisation, whether to trust proxy headers plus an optional list of trusted proxy addresses or CIDR ranges, the off-site anchor, security alerts, roles to exclude from logging, and the uninstall data opt-in. Changes to these settings are themselves logged. The danger zone clears every event and rebuilds the chain; the first event of the new chain records how many events were removed and whether the chain was intact beforehand. Cleanup runs daily via WP-Cron, or during normal traffic on sites where WP-Cron never fires.

Data and privacy

Events live in your own database. Nothing is sent to Dragon Core or any third party; the only data that leaves the site is the optional anchor (an event number and a hash) to the email address and webhook you configure, and, when switched on, security alert emails (naming the site, the users involved and the IP addresses involved) to the addresses you choose. IP capture is optional and can be anonymised, and WooCommerce events recorded with no logged-in user are stored without an IP address; when you trust proxy headers, the address your proxy appended (the last X-Forwarded-For entry) is recorded. Option and field names that look like secrets (password, pass, pwd, secret, token, auth, nonce, salt, private, api, licence, credential, or containing _key) are stored as "[redacted]". Settings stored as JSON or serialized text are summarised in the log row, nested secret-shaped keys are redacted before a change reaches other code listening for events and before a setting's full value is kept for the diff (secret-named settings and the anchor webhook address are never kept there), and the anchor webhook address is recorded as its host only. The full-diff copies of post content and setting values are deleted with their events. The last 20 login addresses per user, kept for new-address alerts, are covered by personal data export and erasure. Plugin bookkeeping settings (names containing _cache or last, or ending in _version, db_version or _lock) and the routine licence re-checks of Dragon Core's paid add-ons are not logged. The plugin registers WordPress privacy-tools integration: export and erasure cover events the user caused, events about their account, and failed logins against their username. For events about the person that someone else caused, the export leaves out that other person's IP address and username. Erasing rows resets the hash chain, which reseals over the remaining events. Uninstalling keeps your log by default: an audit trail you can lose by clicking uninstall isn't much of an audit trail, so opt into deletion in Settings if you want it removed.

WP-CLI

wp dragon-activity-log list [--limit=<n>] [--event=<code>], wp dragon-activity-log prune, wp dragon-activity-log anchor (send an off-site anchor now) and wp dragon-activity-log verify [--anchor=<reference>].

Coming from the Pro add-on

Rollback, full diffs, security alerts, export and WooCommerce events used to be the Dragon Activity Log Pro add-on and are built in from version 1.1.0. While the add-on is still active, the built-in versions stay off and the log screen asks you to deactivate it. Nothing is carried over from the add-on: after deactivating it, switch the security alerts on again under Settings > Security alerts, and expect diffs and rollback for changes recorded from then on (the add-on's earlier diffs are not carried over).

Uninstall

Deleting the plugin keeps all its data by default, so a reinstall picks up where you left off. To remove everything on uninstall, tick Delete all data on uninstall in the plugin's settings first (this sets the dragonactivitylog_delete_data_on_uninstall option; only 1, true, yes or on count as opted in).