Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using HTMX dynamic attributes
Leveraging HTMX for Real-Time Activity Logs in Gutenberg
Integrating dynamic, real-time activity logging directly into the WordPress Gutenberg editor presents a unique challenge. Traditional AJAX polling is often inefficient and can lead to a cluttered user experience. This guide details a robust approach using HTMX, specifically its dynamic attribute capabilities, to build a custom Gutenberg block that displays real-time activity logs without full page reloads or complex JavaScript frameworks. This solution is designed for CTOs and Enterprise Architects seeking efficient, scalable, and maintainable solutions for enhancing WordPress admin interfaces.
Core Architecture: HTMX and WordPress REST API
The fundamental principle is to decouple the log display from the main editor rendering. The Gutenberg block will act as a container, and HTMX will be responsible for fetching and updating the log content from a custom WordPress REST API endpoint. This endpoint will query the WordPress database for recent activity, formatted for efficient rendering.
Step 1: Setting Up the Custom REST API Endpoint
We need a dedicated REST API endpoint to serve the activity logs. This endpoint will be responsible for querying the database and returning the data in a format suitable for HTMX. We’ll use WordPress’s built-in REST API registration capabilities.
Registering the Endpoint
Create a new PHP file within your plugin or theme’s directory (e.g., wp-content/plugins/my-activity-log/my-activity-log.php) and register the endpoint.
<?php
/**
* Plugin Name: My Activity Log
* Description: Adds a real-time activity log block to Gutenberg.
* Version: 1.0
* Author: Your Name
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'my-activity-log/v1', '/logs', array(
'methods' => 'GET',
'callback' => 'my_activity_log_get_logs',
'permission_callback' => function () {
// Ensure only authenticated users can access
return current_user_can( 'read' );
}
) );
} );
function my_activity_log_get_logs( WP_REST_Request $request ) {
// Query for recent activity logs. For simplicity, we'll use a placeholder.
// In a real-world scenario, you'd query a custom table or WP_Query with specific post types.
$logs = array(
array(
'timestamp' => date( 'Y-m-d H:i:s' ),
'user' => wp_get_current_user()->display_name,
'action' => 'User viewed dashboard',
'details' => 'Accessed via REST API',
),
array(
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-1 minute' ) ),
'user' => 'Admin User',
'action' => 'Post published',
'details' => 'Post ID: 123',
),
);
// Simulate fetching from a more complex source
// Example: Querying a custom log table
/*
global $wpdb;
$table_name = $wpdb->prefix . 'activity_logs';
$logs = $wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY timestamp DESC LIMIT 10" );
*/
return new WP_REST_Response( $logs, 200 );
}
?>
In a production environment, you would replace the hardcoded log data with actual database queries. This might involve creating a custom database table for logs or leveraging WordPress’s post types and meta data. The key is to return a JSON array of log entries.
Step 2: Creating the Gutenberg Block
We’ll create a custom Gutenberg block using JavaScript. This block will contain the HTMX attributes to manage the dynamic updates.
Block Registration (JavaScript)
Create a JavaScript file (e.g., wp-content/plugins/my-activity-log/src/block.js) and enqueue it properly. For this example, we’ll assume it’s enqueued via your plugin’s main PHP file.
// wp-content/plugins/my-activity-log/src/block.js
const { registerBlockType } = wp.blocks;
const { ServerSideRender } = wp.components;
const { Fragment } = wp.element;
registerBlockType( 'my-activity-log/real-time-logs', {
title: 'Real-Time Activity Logs',
icon: 'list-view',
category: 'widgets',
edit: () => {
// The edit function for a server-side rendered block.
// We'll handle the HTMX logic in the frontend rendering.
return (
<div>
<h3>Activity Logs (Live)</h3>
<p>Logs will appear here. This block is powered by HTMX.</p>
{ /* A placeholder for the actual rendered content */ }
</div>
);
},
save: () => {
// The save function for a server-side rendered block.
// We don't need to save static HTML for this dynamic block.
// The rendering will be handled by the server.
return null;
},
} );
Enqueuing the Block Script
In your main plugin PHP file (my-activity-log.php), enqueue the script.
<?php
// ... (previous code) ...
function my_activity_log_register_block() {
// Automatically load dependencies and version
$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');
wp_register_script(
'my-activity-log-block-editor',
plugin_dir_url( __FILE__ ) . 'build/index.js',
$asset_file['dependencies'],
$asset_file['version']
);
register_block_type( 'my-activity-log/real-time-logs', array(
'editor_script' => 'my-activity-log-block-editor',
// We'll use a server-side render callback to output the HTMX-enabled HTML
'render_callback' => 'my_activity_log_render_block',
) );
}
add_action( 'init', 'my_activity_log_register_block' );
function my_activity_log_render_block( $attributes ) {
// This function will output the HTML with HTMX attributes.
// It will be called when the block is rendered on the frontend or in the editor preview.
// We need to ensure HTMX is loaded.
wp_enqueue_script( 'htmx-script', 'https://unpkg.com/[email protected]', array(), '1.9.10', true );
ob_start();
?>
<div
id="activity-log-container"
class="activity-log-wrapper"
hx-get=""
hx-trigger="every 5s"
hx-swap="innerHTML"
>
<!-- Logs will be loaded here by HTMX -->
Loading logs...
</div>
<?php
return ob_get_clean();
}
?>
Note: For a real-world plugin, you’d use a build process (like Webpack) to compile your JavaScript and generate the index.asset.php file. The example above simplifies this by assuming a pre-compiled build/index.js and build/index.asset.php.
Step 3: Configuring HTMX Attributes
The magic happens in the my_activity_log_render_block function. We’re adding several key HTMX attributes to a wrapper `div`:
hx-get: Specifies the URL of the REST API endpoint to fetch data from. We userest_url( 'my-activity-log/v1/logs' )to dynamically get the correct REST API URL.hx-trigger: Defines when the request should be made."every 5s"means the logs will be refreshed every 5 seconds. This can be customized (e.g.,loadfor initial load,click, custom events).hx-swap: Determines how the fetched content should be placed into the DOM."innerHTML"replaces the entire content of the `div` with the response from the server.
The initial content of the `div` (“Loading logs…”) serves as a placeholder until the first response is received.
Step 4: Styling the Activity Log
You’ll want to add some CSS to make the logs presentable. Create a CSS file (e.g., wp-content/plugins/my-activity-log/assets/css/style.css) and enqueue it.
/* wp-content/plugins/my-activity-log/assets/css/style.css */
.activity-log-wrapper {
border: 1px solid #ddd;
padding: 15px;
margin-top: 20px;
background-color: #f9f9f9;
max-height: 300px;
overflow-y: auto;
font-size: 0.9em;
border-radius: 4px;
}
.activity-log-wrapper h4 {
margin-top: 0;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.activity-log-item {
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed #eee;
}
.activity-log-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.activity-log-timestamp {
color: #888;
font-style: italic;
margin-right: 10px;
}
.activity-log-action {
font-weight: bold;
color: #555;
}
.activity-log-details {
color: #666;
margin-left: 5px;
}
Enqueue this CSS in your main plugin PHP file:
<?php
// ... (previous code) ...
function my_activity_log_enqueue_styles() {
wp_enqueue_style(
'my-activity-log-style',
plugin_dir_url( __FILE__ ) . 'assets/css/style.css',
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'assets/css/style.css' )
);
}
add_action( 'admin_enqueue_scripts', 'my_activity_log_enqueue_styles' );
// Or 'wp_enqueue_scripts' if you want it on the frontend too.
?>
Step 5: Rendering the Log Entries (Server-Side)
The REST API endpoint currently returns raw JSON. HTMX will insert this JSON directly into the `innerHTML` of the target `div`. To render this nicely, we need to modify the REST API callback to return HTML, or use a client-side template with HTMX. Returning HTML from the server is often simpler for this use case.
Modifying the REST API Callback to Return HTML
We’ll change the `my_activity_log_get_logs` function to generate HTML snippets instead of JSON. This requires a slight adjustment to how the response is handled.
<?php
// ... (previous code) ...
function my_activity_log_get_logs( WP_REST_Request $request ) {
// In a real scenario, fetch logs from DB
$logs_data = array(
array(
'timestamp' => date( 'Y-m-d H:i:s' ),
'user' => wp_get_current_user()->display_name,
'action' => 'User viewed dashboard',
'details' => 'Accessed via REST API',
),
array(
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-1 minute' ) ),
'user' => 'Admin User',
'action' => 'Post published',
'details' => 'Post ID: 123',
),
// Add more log entries for testing
array(
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-2 minutes' ) ),
'user' => 'Editor',
'action' => 'Comment approved',
'details' => 'Comment ID: 456',
),
);
// Generate HTML output
ob_start();
?>
<h4>Recent Activity</h4>
<ul class="activity-log-list">
<?php foreach ( $logs_data as $log ) : ?>
<li class="activity-log-item">
<span class="activity-log-timestamp"><?php echo esc_html( $log['timestamp'] ); ?></span>
<span class="activity-log-action"><?php echo esc_html( $log['action'] ); ?></span>
<span class="activity-log-details">- <?php echo esc_html( $log['details'] ); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php
$html_output = ob_get_clean();
// Return a WP_REST_Response with HTML content
$response = new WP_REST_Response( $html_output, 200 );
$response->set_content_type( 'text/html' ); // Crucial for HTMX to interpret as HTML
return $response;
}
?>
By setting the Content-Type header to text/html, HTMX will correctly interpret the response and swap it into the target element.
Advanced Considerations and Optimizations
Database Performance
For high-traffic sites, querying the WordPress `posts` table for logs can become a bottleneck. Consider creating a dedicated `wp_activity_logs` table with appropriate indexes for `timestamp` and `user_id`. This allows for much faster retrieval of recent events.
Caching
While the logs are “real-time,” there’s a balance. If the underlying data doesn’t change every second, consider implementing server-side caching for the REST API endpoint. WordPress Transients API or object caching (e.g., Redis, Memcached) can be used. HTMX also has built-in caching capabilities (e.g., hx-cache attribute) that can be explored, though server-side caching is generally more robust.
Security and Permissions
The permission_callback in the REST API registration is critical. Ensure it strictly enforces who can view these logs. For enterprise environments, you might need more granular permissions based on user roles or specific capabilities.
Error Handling and Fallbacks
What happens if the REST API request fails? HTMX provides error handling attributes like hx-on::error. You can use this to display a fallback message or trigger alternative actions. Ensure the initial “Loading logs…” message is informative.
Scalability of Updates
The hx-trigger="every 5s" is simple but can lead to many concurrent requests if many users have the block open. For very large-scale deployments, consider alternative triggers or a more sophisticated pub/sub mechanism if true push-based updates are required (though HTMX excels at pull-based updates).
Conclusion
By combining WordPress’s REST API with HTMX’s declarative approach to dynamic updates, we can build highly efficient and maintainable real-time components within the Gutenberg editor. This pattern avoids the overhead of traditional JavaScript frameworks for simple dynamic content, offering a performant and developer-friendly solution for enhancing WordPress admin experiences. The separation of concerns—Gutenberg for UI structure, REST API for data, and HTMX for dynamic interaction—creates a robust and scalable architecture suitable for enterprise-level applications.