• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using PHP block-render callbacks

Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using PHP block-render callbacks

Leveraging PHP Block-Render Callbacks for Real-Time Activity Logs in Gutenberg

For e-commerce platforms, real-time visibility into user activity is paramount for security, performance monitoring, and understanding customer behavior. Integrating this data directly into the WordPress admin interface via Gutenberg offers a powerful, context-aware dashboard. This guide details the construction of a custom Gutenberg block that dynamically displays real-time activity logs, utilizing PHP block-render callbacks for efficient server-side rendering.

Prerequisites and Setup

Before we begin, ensure you have a local development environment set up with WordPress, PHP (version 7.4+ recommended), and Node.js/npm for asset compilation. Familiarity with the WordPress Plugin API and basic JavaScript is assumed.

Defining the Gutenberg Block

We’ll create a custom plugin to house our Gutenberg block. The block’s registration and server-side rendering logic will be defined in PHP. The block’s metadata, including its name, title, and editor script/style handles, is defined in a JavaScript file.

Plugin Structure

Create a new directory for your plugin, e.g., wp-content/plugins/ecommerce-activity-log. Inside, create the following files and directories:

  • ecommerce-activity-log.php (main plugin file)
  • build/ (directory for compiled JS/CSS)
  • src/ (directory for source JS/CSS)
  • src/index.js (main JavaScript entry point for the block)
  • src/block.json (block metadata)

Block Metadata (src/block.json)

This file describes our block to WordPress. We’ll specify a render_callback here, pointing to a PHP function that will handle the server-side rendering.

{
    "apiVersion": 2,
    "name": "ecommerce-activity-log/realtime-logs",
    "title": "Real-time Activity Log",
    "category": "widgets",
    "icon": "chart-line",
    "description": "Displays a real-time feed of user activity.",
    "keywords": [ "ecommerce", "activity", "log", "real-time" ],
    "attributes": {
        "logCount": {
            "type": "number",
            "default": 10
        }
    },
    "editorScript": "file:./build/index.js",
    "editorStyle": "file:./build/index.css",
    "style": "file:./build/style-index.css",
    "render_callback": "ecommerce_activity_log_render_callback"
}

JavaScript Entry Point (src/index.js)

This file registers the block for the editor. For a block with a render_callback, the editor representation can be quite simple, often just a placeholder or a static preview. We’ll use a basic placeholder here.

import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import './style.scss'; // Editor styles
import './editor.scss'; // Editor-specific styles

registerBlockType( 'ecommerce-activity-log/realtime-logs', {
    title: __( 'Real-time Activity Log', 'ecommerce-activity-log' ),
    icon: 'chart-line',
    category: 'widgets',
    description: __( 'Displays a real-time feed of user activity.', 'ecommerce-activity-log' ),
    attributes: {
        logCount: {
            type: 'number',
            default: 10,
        },
    },
    edit: ( { attributes, setAttributes } ) => {
        // Simple placeholder for the editor.
        // The actual content is rendered server-side.
        return (
            <div>
                <h3>{ __( 'Real-time Activity Log', 'ecommerce-activity-log' ) }</h3>
                <p>{ __( 'Displaying the last', 'ecommerce-activity-log' ) } { attributes.logCount } { __( ' activities.', 'ecommerce-activity-log' ) }</p>
                <p><em>{ __( '(Content will be rendered on the front-end)', 'ecommerce-activity-log' ) }</em></p>
            </div>
        );
    },
    save: () => {
        // This function is not used when a render_callback is defined.
        // WordPress will use the output of the render_callback instead.
        return null;
    },
} );

Asset Compilation

To compile the JavaScript and SCSS files, you’ll need a build process. A common setup uses `@wordpress/scripts`. Add a package.json file to your plugin’s root directory:

{
    "name": "ecommerce-activity-log",
    "version": "1.0.0",
    "description": "Custom Gutenberg block for e-commerce activity logs.",
    "main": "build/index.js",
    "scripts": {
        "build": "wp-scripts build",
        "start": "wp-scripts start"
    },
    "keywords": [],
    "author": "",
    "license": "GPL-2.0-or-later",
    "devDependencies": {
        "@wordpress/scripts": "^26.0.0"
    }
}

Run npm install to install dependencies, then npm run build to compile your assets. The compiled files will appear in the build/ directory.

Server-Side Rendering with PHP Block-Render Callbacks

The core of our real-time functionality lies in the PHP render_callback. This function receives the block’s attributes and is responsible for outputting the HTML that will be displayed on the front-end.

Plugin Main File (ecommerce-activity-log.php)

This file registers the block type and defines the render callback function.

<?php
/**
 * Plugin Name: E-commerce Activity Log
 * Description: Displays a real-time feed of user activity for e-commerce.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 * Text Domain: ecommerce-activity-log
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

/**
 * Registers the block using the metadata loaded from the `block.json` file.
 * Behind the scenes, it registers also all assets so they can be enqueued
 * through the block editor in the corresponding context.
 *
 * @see https://developer.wordpress.org/reference/functions/register_block_type/
 */
function ecommerce_activity_log_block_init() {
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'ecommerce_activity_log_block_init' );

/**
 * Render callback function for the Real-time Activity Log block.
 *
 * @param array $attributes The block attributes.
 * @return string The HTML output for the block.
 */
function ecommerce_activity_log_render_callback( $attributes ) {
    $log_count = isset( $attributes['logCount'] ) ? intval( $attributes['logCount'] ) : 10;

    // Ensure we don't try to fetch a negative number of logs.
    if ( $log_count <= 0 ) {
        $log_count = 10;
    }

    // In a real-world scenario, you would fetch logs from a custom table,
    // an external service, or a more sophisticated logging mechanism.
    // For this example, we'll simulate fetching recent WordPress actions.
    $logs = ecommerce_activity_log_get_simulated_logs( $log_count );

    if ( empty( $logs ) ) {
        return '<p>' . __( 'No activity logs found.', 'ecommerce-activity-log' ) . '</p>';
    }

    ob_start();
    ?>
    <div class="ecommerce-activity-log-container">
        <h3><?php esc_html_e( 'Recent Activity', 'ecommerce-activity-log' ); ?></h3>
        <ul class="ecommerce-activity-log-list">
            <?php foreach ( $logs as $log ) : ?>
                <li class="ecommerce-activity-log-item">
                    <span class="ecommerce-activity-log-timestamp"><?php echo esc_html( $log['timestamp'] ); ?></span>
                    <span class="ecommerce-activity-log-message"><?php echo esc_html( $log['message'] ); ?></span>
                    <span class="ecommerce-activity-log-user">(<?php echo esc_html( $log['user'] ); ?>)</span>
                </li>
            <?php endforeach; ?>
        </ul>
        <!-- Add a placeholder for real-time updates -->
        <div id="ecommerce-activity-log-realtime-indicator"></div>
    </div>
    <?php
    return ob_get_clean();
}

/**
 * Simulates fetching activity logs.
 * In a production environment, this would query a dedicated log table or API.
 *
 * @param int $count Number of logs to retrieve.
 * @return array Array of log entries.
 */
function ecommerce_activity_log_get_simulated_logs( $count = 10 ) {
    $sample_logs = [
        [ 'timestamp' => '2023-10-27 10:00:05', 'message' => 'User "john_doe" logged in.', 'user' => 'System' ],
        [ 'timestamp' => '2023-10-27 10:01:15', 'message' => 'Product "Awesome Gadget" added to cart by user "jane_smith".', 'user' => 'jane_smith' ],
        [ 'timestamp' => '2023-10-27 10:02:30', 'message' => 'Order #12345 placed by user "john_doe".', 'user' => 'john_doe' ],
        [ 'timestamp' => '2023-10-27 10:03:00', 'message' => 'User "admin" updated product "Super Widget".', 'user' => 'admin' ],
        [ 'timestamp' => '2023-10-27 10:04:45', 'message' => 'User "jane_smith" completed checkout for order #12346.', 'user' => 'jane_smith' ],
        [ 'timestamp' => '2023-10-27 10:05:10', 'message' => 'New user registered: "peter_jones".', 'user' => 'System' ],
        [ 'timestamp' => '2023-10-27 10:06:20', 'message' => 'Product "Basic T-Shirt" stock updated.', 'user' => 'admin' ],
        [ 'timestamp' => '2023-10-27 10:07:00', 'message' => 'User "john_doe" viewed product "Awesome Gadget".', 'user' => 'john_doe' ],
        [ 'timestamp' => '2023-10-27 10:08:15', 'message' => 'Comment on product "Super Widget" by user "jane_smith".', 'user' => 'jane_smith' ],
        [ 'timestamp' => '2023-10-27 10:09:30', 'message' => 'User "peter_jones" added "Basic T-Shirt" to wishlist.', 'user' => 'peter_jones' ],
    ];

    // Return the latest logs up to the requested count.
    return array_slice( array_reverse( $sample_logs ), 0, $count );
}
?>

Implementing Real-Time Updates

The PHP render callback provides the initial HTML. For true real-time updates, we need a client-side mechanism. This typically involves WebSockets or Server-Sent Events (SSE). For simplicity in this example, we’ll simulate updates using JavaScript polling, which is less efficient but easier to implement without a dedicated WebSocket server.

Client-Side JavaScript for Updates (src/index.js – Modified)

We’ll add JavaScript to the editor and front-end to periodically fetch new log entries. For the front-end, we’ll enqueue a separate script.

Enqueueing Front-End Script

Add this to your ecommerce-activity-log.php file:

/**
 * Enqueue front-end scripts.
 */
function ecommerce_activity_log_enqueue_scripts() {
    // Only enqueue on the front-end if the block is present.
    // A more robust check would involve checking post content for the block signature.
    if ( is_admin() ) {
        return;
    }

    wp_enqueue_script(
        'ecommerce-activity-log-frontend',
        plugin_dir_url( __FILE__ ) . 'build/frontend.js',
        array( 'wp-element' ), // Dependencies
        filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' ),
        true // Load in footer
    );

    // Pass data to the script, e.g., the initial log count and nonce for AJAX.
    wp_localize_script( 'ecommerce-activity-log-frontend', 'ecommerceActivityLog', array(
        'logCount' => 10, // Default or fetched from block attributes if possible
        'ajaxUrl' => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'ecommerce_activity_log_nonce' ),
    ) );
}
add_action( 'wp_enqueue_scripts', 'ecommerce_activity_log_enqueue_scripts' );

Front-End JavaScript (src/frontend.js)

Create a new file src/frontend.js and add the following:

document.addEventListener( 'DOMContentLoaded', () => {
    const logContainer = document.querySelector( '.ecommerce-activity-log-container' );
    if ( ! logContainer ) {
        return; // Block not present on this page
    }

    const logList = logContainer.querySelector( '.ecommerce-activity-log-list' );
    const realtimeIndicator = document.getElementById( 'ecommerce-activity-log-realtime-indicator' );
    const initialLogCount = typeof ecommerceActivityLog !== 'undefined' ? ecommerceActivityLog.logCount : 10;
    let currentLogCount = initialLogCount;

    // Function to fetch and display new logs
    const fetchAndUpdateLogs = () => {
        fetch( ecommerceActivityLog.ajaxUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams( {
                action: 'ecommerce_activity_log_fetch_logs',
                nonce: ecommerceActivityLog.nonce,
                count: currentLogCount, // Requesting current count to get *new* logs
            } ),
        } )
        .then( response => response.json() )
        .then( data => {
            if ( data.success && data.data.logs && data.data.logs.length > 0 ) {
                // Prepend new logs to the list
                data.data.logs.forEach( log => {
                    const listItem = document.createElement( 'li' );
                    listItem.className = 'ecommerce-activity-log-item';
                    listItem.innerHTML = `
                        <span class="ecommerce-activity-log-timestamp">${ log.timestamp }</span>
                        <span class="ecommerce-activity-log-message">${ log.message }</span>
                        <span class="ecommerce-activity-log-user">(${ log.user })</span>
                    `;
                    logList.prepend( listItem ); // Add to the top
                } );
                currentLogCount += data.data.logs.length; // Update count

                // Keep the list size manageable (optional)
                const maxItems = 50; // Example limit
                while ( logList.children.length > maxItems ) {
                    logList.removeChild( logList.lastChild );
                }

                if ( realtimeIndicator ) {
                    realtimeIndicator.textContent = `(${ data.data.logs.length } new activities) ${ new Date().toLocaleTimeString() }`;
                    setTimeout(() => { realtimeIndicator.textContent = ''; }, 5000); // Clear indicator after 5 seconds
                }
            } else if ( data.success && data.data.logs && data.data.logs.length === 0 ) {
                 // No new logs, do nothing
                 if ( realtimeIndicator ) {
                    realtimeIndicator.textContent = `(No new activities) ${ new Date().toLocaleTimeString() }`;
                    setTimeout(() => { realtimeIndicator.textContent = ''; }, 5000);
                 }
            } else {
                console.error( 'Failed to fetch logs:', data.data.message || 'Unknown error' );
                 if ( realtimeIndicator ) {
                    realtimeIndicator.textContent = `(Error fetching logs) ${ new Date().toLocaleTimeString() }`;
                 }
            }
        } )
        .catch( error => {
            console.error( 'Error fetching logs:', error );
             if ( realtimeIndicator ) {
                realtimeIndicator.textContent = `(Network error) ${ new Date().toLocaleTimeString() }`;
             }
        } );
    };

    // Poll for new logs every 15 seconds
    const pollingInterval = 15000;
    setInterval( fetchAndUpdateLogs, pollingInterval );

    // Initial fetch on load (optional, as render_callback already provides initial logs)
    // fetchAndUpdateLogs();
} );

AJAX Handler for Log Fetching

We need a WordPress AJAX endpoint to serve new log entries to the front-end JavaScript. Add this to ecommerce-activity-log.php:

/**
 * AJAX handler to fetch recent activity logs.
 */
function ecommerce_activity_log_fetch_logs_ajax() {
    check_ajax_referer( 'ecommerce_activity_log_nonce', 'nonce' );

    $current_log_count = isset( $_POST['count'] ) ? intval( $_POST['count'] ) : 10;

    // Fetch logs *since* the last known log.
    // This requires a way to track the latest log timestamp or ID.
    // For simulation, we'll fetch a slightly larger batch and return only the newest ones.
    $fetch_batch_size = 5; // Fetch a few more than requested to find new ones
    $simulated_all_logs = ecommerce_activity_log_get_simulated_logs( $current_log_count + $fetch_batch_size ); // Get more logs than currently displayed

    $new_logs = [];
    if ( ! empty( $simulated_all_logs ) ) {
        // Assuming logs are sorted by time descending, the first $current_log_count are already displayed.
        // We take the next $fetch_batch_size logs as potentially new.
        $new_logs = array_slice( $simulated_all_logs, 0, $fetch_batch_size );
    }

    if ( ! empty( $new_logs ) ) {
        wp_send_json_success( [
            'logs' => $new_logs,
            'message' => sprintf( __( '%d new activity logs fetched.', 'ecommerce-activity-log' ), count( $new_logs ) ),
        ] );
    } else {
        wp_send_json_success( [
            'logs' => [],
            'message' => __( 'No new activity logs found.', 'ecommerce-activity-log' ),
        ] );
    }
}
add_action( 'wp_ajax_ecommerce_activity_log_fetch_logs', 'ecommerce_activity_log_fetch_logs_ajax' );
// For non-logged-in users, though unlikely for activity logs.
// add_action( 'wp_ajax_nopriv_ecommerce_activity_log_fetch_logs', 'ecommerce_activity_log_fetch_logs_ajax' );

Styling the Block (src/style.scss and src/editor.scss)

Add some basic styling to make the log readable. Create src/style.scss for front-end styles and src/editor.scss for editor styles. Ensure your package.json includes these in the build process (@wordpress/scripts handles this automatically if they are in the src/ directory).

/* src/style.scss */
.ecommerce-activity-log-container {
    border: 1px solid #ddd;
    padding: 15px;
    background-color: #f9f9f9;
    border-radius: 4px;
    font-family: sans-serif;
    margin-bottom: 20px;
}

.ecommerce-activity-log-container h3 {
    margin-top: 0;
    color: #333;
    border-bottom: 1px solid #eee;
    padding-bottom: 10px;
    margin-bottom: 15px;
}

.ecommerce-activity-log-list {
    list-style: none;
    padding: 0;
    margin: 0;
    max-height: 300px; /* Limit height and allow scrolling */
    overflow-y: auto;
}

.ecommerce-activity-log-item {
    padding: 8px 0;
    border-bottom: 1px dashed #eee;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    font-size: 0.9em;
    color: #555;
}

.ecommerce-activity-log-item:last-child {
    border-bottom: none;
}

.ecommerce-activity-log-timestamp {
    color: #888;
    flex-shrink: 0;
}

.ecommerce-activity-log-message {
    flex-grow: 1;
    color: #333;
}

.ecommerce-activity-log-user {
    color: #777;
    font-style: italic;
    flex-shrink: 0;
}

#ecommerce-activity-log-realtime-indicator {
    margin-top: 10px;
    font-size: 0.8em;
    color: #999;
    text-align: right;
    min-height: 1.2em; /* Prevent layout shift */
}
/* src/editor.scss */
.wp-block-ecommerce-activity-log-realtime-logs {
    border: 1px dashed #ccc;
    padding: 15px;
    background-color: #f0f0f0;
    text-align: center;
    font-family: sans-serif;
}

.wp-block-ecommerce-activity-log-realtime-logs h3 {
    margin-top: 0;
    color: #555;
}

Real-World Considerations and Enhancements

The provided solution uses simulated logs and polling. For a production e-commerce environment, consider these improvements:

  • Dedicated Logging System: Implement a custom database table (e.g., wp_ecommerce_activity_logs) to store structured log data. This allows for more efficient querying and filtering than parsing WordPress actions.
  • WebSockets/SSE: Replace polling with WebSockets (e.g., using Socket.IO or a WordPress plugin like WP-Websockets) or Server-Sent Events for true real-time, push-based updates. This significantly reduces server load and improves responsiveness.
  • Security: Implement robust nonce checks and sanitize all data before outputting it. Ensure user roles and capabilities are checked if certain logs are sensitive.
  • Performance: For high-traffic sites, optimize log retrieval. Consider caching strategies or using a dedicated logging service. The AJAX endpoint should be highly efficient.
  • Filtering and Search: Add controls within the block (either in the editor or on the front-end) to filter logs by user, activity type, or date range.
  • Error Handling: Enhance error handling in both PHP and JavaScript to provide informative feedback to administrators.
  • User Interface: Improve the visual presentation of logs, perhaps with icons or different styling for different event types.

Conclusion

By utilizing PHP block-render callbacks, you can create dynamic, server-rendered Gutenberg blocks that seamlessly integrate complex functionality into the WordPress admin and front-end. This approach ensures that the block’s content is always up-to-date and efficiently generated. For real-time features like activity logs, combining server-side rendering with a client-side update mechanism (even a simple polling one for demonstration) provides a powerful solution for e-commerce businesses seeking immediate insights into user interactions.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Step-by-Step Guide: Offloading high-frequency knowledge base document categories metadata writes to a Redis KV store
  • How to analyze and reduce CPU consumption of custom Singleton Registry Pattern event mediators
  • How to analyze and reduce CPU consumption of custom Factory Method design structures event mediators
  • WordPress Development Recipe: High-efficiency server-side rendering for Gutenberg blocks using Readonly classes
  • How to securely integrate SendGrid transactional mailer endpoints into WordPress custom plugins using Filesystem API

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (42)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (115)
  • WordPress Plugin Development (123)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Step-by-Step Guide: Offloading high-frequency knowledge base document categories metadata writes to a Redis KV store
  • How to analyze and reduce CPU consumption of custom Singleton Registry Pattern event mediators
  • How to analyze and reduce CPU consumption of custom Factory Method design structures event mediators

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala