Skip to content

Audit Log

There are two logs, and they answer different questions.

LogLives inRead bySurvives a compromised admin
instance-audit-logPHP error log or syslogWhoever collects your logs
Simple HistoryThe WordPress databaseAdministrators, in the admin

Simple History is the convenient one — searchable, in the admin, next to the work. The audit log is the one that still exists after somebody with database access tidies up behind themselves.

instance-audit-log writes one JSON line per event:

{"type":"wp_audit","event":"user_role_changed","time":"2026-08-27T09:14:22+00:00","site":"example.com","user_id":1,"user_login":"joren","ip":"203.0.113.10","forwarded_for":null,"request_uri":"/wp/wp-admin/user-edit.php","context":{"target_user_id":7,"new_role":"administrator","old_roles":["editor"]}}
AreaEvents
Authenticationlogin, login_failed, logout, password_reset_requested, password_reset_completed
Accountsuser_created, user_deleted, user_role_changed, user_updated
Code and configplugin_activated, plugin_deactivated, theme_switched, option_updated
Content and mediapost_status_changed, post_deleted, attachment_added, attachment_deleted
Bulk exportexport_started
Lockoutslogin_lockout, login_lockout_address, login_lockout_released

option_updated covers a short watch list rather than every option — logging all of them is unreadable. The list is the ones that change who can get in or where the site points: siteurl, home, admin_email, users_can_register, default_role, template, stylesheet, blog_public.

add_filter('instance_audit_log_watched_options', function (array $options): array {
$options[] = 'woocommerce_store_address';
return $options;
});

Lines go to the PHP error log by default, which is what most hosting collects. Switch to the system logger when the host ships to journald or rsyslog:

config/application.php
Config::define('INSTANCE_AUDIT_LOG_TRANSPORT', 'syslog');
if (function_exists('instance_audit_log')) {
instance_audit_log('invoice_exported', [
'invoice_id' => $invoice->ID,
'format' => 'pdf',
]);
}

Each line carries two: ip is REMOTE_ADDR, the only address the application can trust, and forwarded_for is the raw X-Forwarded-For header, recorded as a hint because the client controls it.

instance-simple-history-config sets defaults for the plugin and is inert when it is not installed.

DefaultValueWhy
Retention90 daysEntries hold usernames and IP addresses, which are personal data
View history capabilitymanage_optionsReading the log is an administrator task, not an editor one
View settings capabilitymanage_optionsAs above
Dashboard widgetOffThe log has its own admin page
add_filter('instance_simple_history_retention_days', function (): int {
return 30;
});