Implementing Activity Logging: Auditing REST API Changes in WordPress
Learn to build a robust audit system by hooking into the WordPress REST API, logging changes to a custom table, and displaying the history in your React UI.
Previously in this course, we explored handling conflict resolution to ensure data integrity. In this lesson, we take that a step further by implementing a persistent audit trail. Tracking every change made via the REST API is essential for compliance, debugging, and user accountability.
The Architecture of an Audit Log
When building professional-grade plugins, you shouldn't rely on the standard wp_posts revisions system for everything. For a dedicated Knowledge Base, we want a lightweight, queryable audit log stored in a custom database table.
This approach involves three distinct layers:
- The Hook: Intercepting the
rest_after_insert_{$post_type}action to capture data changes. - The Storage: A custom SQL table (
wp_kb_activity_logs) to store immutable events. - The UI: A new endpoint and React component to fetch and display this history.
Step 1: Creating the Custom Audit Table
First, we need a place to store the data. Using a dedicated table keeps our audit logs separate from the heavy lifting of post metadata.
PHPfunction kb_create_activity_log_table() { global $wpdb; $table_name = $wpdb->prefix . 'kb_activity_logs'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, post_id mediumint(9) NOT NULL, user_id mediumint(9) NOT NULL, action varchar(20) NOT NULL, timestamp datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); }
Step 2: Hooking into REST API Updates
WordPress provides the rest_after_insert_{$post_type} hook, which triggers after a post has been saved via the REST API. This is the perfect moment to log the action.
PHPadd_action('rest_after_insert_knowledge_base', 'kb_log_post_update', 10, 3); function kb_log_post_update($post, $request, $creating) { global $wpdb; $table_name = $wpdb->prefix . 'kb_activity_logs'; $wpdb->insert($table_name, [ 'post_id' => $post->ID, 'user_id' => get_current_user_id(), 'action' => $creating ? 'created' : 'updated', 'timestamp' => current_time('mysql') ]); }
Note: We rely on the $creating boolean provided by the hook to distinguish between new entries and updates.
Step 3: Exposing Logs to the React Dashboard
To display these logs, register a new read-only endpoint. As we discussed in implementing REST API permission callbacks, ensure you restrict this to users who have the capability to edit knowledge base items.
PHPregister_rest_route('kb/v1', '/logs/(?P<id>\d+)', [ 'methods' => 'GET', 'callback' => 'kb_get_post_logs', 'permission_callback' => fn() => current_user_can('edit_posts'), ]); function kb_get_post_logs($request) { global $wpdb; $post_id = $request['id']; $table = $wpdb->prefix . 'kb_activity_logs'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table WHERE post_id = %d ORDER BY timestamp DESC", $post_id )); }
Hands-on Exercise
- Migrate: Add the
kb_create_activity_log_tablefunction to your plugin activation hook. - Log: Implement the
kb_log_post_updatehook in your main plugin file. - Fetch: Create a new service function in your
service.jsfile (following the patterns in building the Knowledge Base service layer) that calls the/logs/{id}endpoint. - Display: Create a
LogViewcomponent that renders a list of these entries in your React dashboard.
Common Pitfalls
- Performance Impact: Never run heavy queries inside the
rest_after_inserthook. The database insertion should be a simple, indexed write. - Missing Context: Logging that an update happened is good, but logging what changed is better. For production, consider serializing a diff of the post content into a
detailscolumn in your table. - Security: Always ensure your
GETendpoint for logs is protected. Audit logs can inadvertently leak sensitive information if accessible to unauthorized users.
As mentioned in our API architecture audit logs discussion, logs should be treated as immutable records of truth. Once written, they should rarely be modified.
Recap
We've successfully hooked into the WordPress REST API lifecycle, created a custom table for persistent data, and exposed that data via a secure endpoint. This audit trail is now a core part of our plugin's reliability.
Up next: We will learn how to use Webpack Aliases to clean up our import paths and simplify our project structure.
Work with me

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard — by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.