• 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 audit dashboard block for Gutenberg using REST API custom routes

Step-by-Step Guide to building a custom real-time audit dashboard block for Gutenberg using REST API custom routes

Setting Up Custom REST API Routes for Audit Data

To build a real-time audit dashboard, we first need a way to expose our audit data via the WordPress REST API. This involves creating custom routes that will serve the data our Gutenberg block will consume. We’ll define these routes within a custom plugin to keep our codebase organized and maintainable.

Let’s start by creating a basic plugin structure. Create a new folder named real-time-audit-dashboard in your wp-content/plugins/ directory. Inside this folder, create a main plugin file, for example, real-time-audit-dashboard.php.

Plugin File: real-time-audit-dashboard.php

<?php
/**
 * Plugin Name: Real-time Audit Dashboard
 * Description: Provides a custom REST API endpoint and Gutenberg block for real-time audit data.
 * Version: 1.0
 * Author: Your Name
 */

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

/**
 * Register custom REST API routes.
 */
function rtad_register_api_routes() {
    // Route for fetching audit logs
    register_rest_route( 'rtad/v1', '/logs', array(
        'methods'  => 'GET',
        'callback' => 'rtad_get_audit_logs',
        'permission_callback' => function() {
            // Ensure only authenticated users with 'manage_options' capability can access
            return current_user_can( 'manage_options' );
        }
    ) );
}
add_action( 'rest_api_init', 'rtad_register_api_routes' );

/**
 * Callback function to retrieve audit logs.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 */
function rtad_get_audit_logs( WP_REST_Request $request ) {
    // In a real-world scenario, you would query your audit log database or system here.
    // For demonstration, we'll return dummy data.
    $dummy_logs = array(
        array(
            'id' => 1,
            'timestamp' => current_time( 'mysql' ),
            'user_id' => 1,
            'username' => 'admin',
            'action' => 'User logged in',
            'details' => 'Successful login from IP 192.168.1.100',
        ),
        array(
            'id' => 2,
            'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ),
            'user_id' => 2,
            'username' => 'editor',
            'action' => 'Post published',
            'details' => 'Published post "My Awesome Article" (ID: 123)',
        ),
        array(
            'id' => 3,
            'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-10 minutes' ) ),
            'user_id' => 1,
            'username' => 'admin',
            'action' => 'Settings updated',
            'details' => 'Updated site title to "New Site Title"',
        ),
    );

    // You might want to add pagination, filtering, etc. here based on $request parameters.
    // For simplicity, we return all dummy logs.

    $response = new WP_REST_Response( $dummy_logs, 200 );
    $response->header( 'X-WP-Total', count( $dummy_logs ) );
    $response->header( 'X-WP-TotalPages', 1 ); // Assuming no pagination for this example

    return $response;
}

// Placeholder for Gutenberg block registration and rendering
// This will be covered in subsequent sections.
?>

In this file:

  • We define the basic plugin header.
  • The rtad_register_api_routes function hooks into rest_api_init to register our custom endpoint.
  • We use register_rest_route to define the endpoint /rtad/v1/logs.
  • The methods are set to GET, indicating we’ll be fetching data.
  • The callback is set to rtad_get_audit_logs, which will handle the request.
  • A permission_callback is crucial for security, ensuring only users with the manage_options capability can access this sensitive data.
  • The rtad_get_audit_logs function currently returns dummy data. In a production environment, this is where you’d integrate with your actual audit logging mechanism (e.g., a custom table, a third-party service).

After activating this plugin, you can test the endpoint by visiting your-wordpress-site.com/wp-json/rtad/v1/logs in your browser (while logged in as an administrator). You should see a JSON response with the dummy audit logs.

Developing the Gutenberg Block: JavaScript and JSX

Now, let’s build the Gutenberg block that will display this audit data. Gutenberg blocks are developed using JavaScript (and often React/JSX). We’ll need to enqueue a JavaScript file for our block.

Plugin File: real-time-audit-dashboard.php (Continued)

<?php
// ... (previous code for API routes) ...

/**
 * Enqueue block editor assets.
 */
function rtad_enqueue_block_assets() {
    // Enqueue the JavaScript file for the block editor.
    wp_enqueue_script(
        'rtad-audit-dashboard-block-editor', // Unique handle.
        plugin_dir_url( __FILE__ ) . 'build/index.js', // Path to the JS file.
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-api-fetch' ), // Dependencies.
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' ) // Version based on file modification time.
    );

    // Enqueue the CSS file for the block editor.
    wp_enqueue_style(
        'rtad-audit-dashboard-block-editor-css', // Unique handle.
        plugin_dir_url( __FILE__ ) . 'build/index.css', // Path to the CSS file.
        array( 'wp-edit-blocks' ), // Dependency.
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' ) // Version.
    );
}
add_action( 'enqueue_block_editor_assets', 'rtad_enqueue_block_assets' );

/**
 * Register the block.
 */
function rtad_register_audit_dashboard_block() {
    register_block_type( 'rtad/audit-dashboard', array(
        'editor_script' => 'rtad-audit-dashboard-block-editor',
        'editor_style'  => 'rtad-audit-dashboard-block-editor-css',
        'render_callback' => 'rtad_render_audit_dashboard_block', // For front-end rendering if needed
    ) );
}
add_action( 'init', 'rtad_register_audit_dashboard_block' );

/**
 * Server-side rendering callback for the block (optional, for front-end).
 *
 * @param array $attributes Block attributes.
 * @return string HTML output.
 */
function rtad_render_audit_dashboard_block( $attributes ) {
    // This callback is for rendering the block on the front-end.
    // For a real-time dashboard, you'd likely rely on JavaScript to fetch and update data.
    // However, you could pre-render a loading state or basic structure here.
    return '<div class="rtad-audit-dashboard-frontend">Loading audit data...</div>';
}

?>

Here’s what’s new:

  • rtad_enqueue_block_assets: This function hooks into enqueue_block_editor_assets to load our JavaScript and CSS files specifically for the block editor.
  • We’re using wp_enqueue_script and wp_enqueue_style.
  • Dependencies include wp-blocks, wp-element, wp-editor, wp-components (for UI elements), and crucially, wp-api-fetch (WordPress’s built-in utility for making REST API requests).
  • The script path is build/index.js and the style path is build/index.css. This implies a build process (like Webpack or Rollup) will compile our source JavaScript/JSX into these files.
  • rtad_register_audit_dashboard_block: This function hooks into init and uses register_block_type to register our block with WordPress. We specify the block name (rtad/audit-dashboard) and link it to the enqueued editor script and style.
  • rtad_render_audit_dashboard_block: This is an optional server-side rendering callback. For a truly real-time dashboard, client-side JavaScript will handle data fetching and updates. This callback might be used to render a placeholder or initial state on the front-end.

Build Process Setup (Node.js, npm/yarn)

To compile our JavaScript and JSX into the build/index.js and build/index.css files, we need a build tool. A common setup for WordPress plugins involves Node.js, npm (or yarn), and a bundler like Webpack.

First, create a package.json file in the root of your plugin directory (real-time-audit-dashboard/).

package.json

{
  "name": "real-time-audit-dashboard",
  "version": "1.0.0",
  "description": "Gutenberg block for real-time audit dashboard.",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "keywords": [
    "wordpress",
    "gutenberg",
    "block"
  ],
  "author": "Your Name",
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@wordpress/scripts": "^26.0.0"
  }
}

In your plugin’s root directory, run:

npm install

This will install the necessary development dependencies, including @wordpress/scripts, which provides pre-configured scripts for building WordPress packages. The scripts section defines commands:

  • npm run build: Compiles your source files into production-ready build/index.js and build/index.css.
  • npm run start: Watches your source files for changes and automatically recompiles them, useful during development.

You’ll need to create a src/ directory within your plugin folder. Inside src/, create index.js and index.scss (or index.css).

Source File: src/index.js

/**
 * WordPress dependencies.
 */
const { registerBlockType } = wp.blocks;
const { Component } = wp.element;
const { apiFetch } = wp;
const { InspectorControls, PanelColorSettings } = wp.editor;
const { PanelBody, Spinner, Table, TableRow, TableCell, TableHead, TableBody, Placeholder, Button } = wp.components;

/**
 * Internal dependencies.
 */
import './style.scss'; // Import styles for both editor and front-end.
import './editor.scss'; // Import styles specific to the editor.

/**
 * Audit Log Data Fetcher Component.
 */
class AuditLogFetcher extends Component {
    constructor(props) {
        super(props);
        this.state = {
            logs: [],
            isLoading: true,
            error: null,
        };
    }

    componentDidMount() {
        this.fetchLogs();
    }

    fetchLogs() {
        this.setState({ isLoading: true, error: null });
        apiFetch({ path: '/rtad/v1/logs' })
            .then(logs => {
                this.setState({ logs, isLoading: false });
            })
            .catch(error => {
                this.setState({ error, isLoading: false });
                console.error('Error fetching audit logs:', error);
            });
    }

    render() {
        const { logs, isLoading, error } = this.state;
        const { backgroundColor } = this.props; // Get color from attributes

        if (isLoading) {
            return (
                <Placeholder icon="admin-generic" label="Audit Dashboard">
                    <Spinner />
                    <p>Loading audit data...</p>
                </Placeholder>
            );
        }

        if (error) {
            return (
                <Placeholder icon="warning" label="Audit Dashboard">
                    <p>Error loading audit data. Please try again.</p>
                    <Button isDefault onClick={ () => this.fetchLogs() }>
                        Retry
                    </Button>
                </Placeholder>
            );
        }

        if (logs.length === 0) {
            return (
                <Placeholder icon="info" label="Audit Dashboard">
                    <p>No audit logs found.</p>
                </Placeholder>
            );
        }

        // Apply background color if set
        const blockStyle = {
            backgroundColor: backgroundColor ? backgroundColor : '#f0f0f0', // Default background
            padding: '20px',
            borderRadius: '5px',
        };

        return (
            <div style={ blockStyle }>
                <h3>Recent Audit Logs</h3>
                <Table>
                    <TableHead>
                        <TableRow>
                            <TableCell>Timestamp</TableCell>
                            <TableCell>User</TableCell>
                            <TableCell>Action</TableCell>
                            <TableCell>Details</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        { logs.map( log => (
                            <TableRow key={ log.id }>
                                <TableCell>{ log.timestamp }</TableCell>
                                <TableCell>{ log.username }</TableCell>
                                <TableCell>{ log.action }</TableCell>
                                <TableCell>{ log.details }</TableCell>
                            </TableRow>
                        )) }
                    </TableBody>
                </Table>
            </div>
        );
    }
}

/**
 * Block Settings.
 */
const settings = {
    title: 'Audit Dashboard',
    icon: 'chart-bar', // Or any other suitable icon
    category: 'widgets', // Or 'design', 'plugins', etc.
    attributes: {
        backgroundColor: {
            type: 'string',
            default: '#f0f0f0',
        },
    },
    edit: function(props) {
        const { attributes, setAttributes } = props;
        const { backgroundColor } = attributes;

        const onChangeBackgroundColor = ( newColor ) => {
            setAttributes( { backgroundColor: newColor } );
        };

        return (
            <div className="rtad-audit-dashboard-editor">
                <InspectorControls>
                    <PanelBody title="Display Settings" initialOpen={ true }>
                        <PanelColorSettings
                            title="Background Color"
                            initialOpen={ true }
                            colorSettings={ [
                                {
                                    value: backgroundColor,
                                    onChange: onChangeBackgroundColor,
                                    label: 'Background Color',
                                },
                            ] }
                        />
                    </PanelBody>
                </InspectorControls>
                <AuditLogFetcher backgroundColor={ backgroundColor } />
            </div>
        );
    },
    save: function(props) {
        const { attributes } = props;
        const { backgroundColor } = attributes;

        // The save function should return static HTML or null if rendering is handled by PHP.
        // For a dynamic block like this, we often return null and rely on server-side rendering
        // or client-side rendering on the front-end.
        // If we want to render something static on save, it would be:
        // return (
        //     <div style={ { backgroundColor: backgroundColor, padding: '20px', borderRadius: '5px' } }>
        //         <p>Audit Dashboard (will load dynamically)</p>
        //     </div>
        // );

        // Returning null means the block will be rendered by the server_render_callback or client-side JS.
        // Since we have a server_render_callback, we can use that.
        // If not, the block will be empty on the front-end until JS loads.
        return null;
    },
};

registerBlockType( 'rtad/audit-dashboard', settings );

And a basic SCSS file for styling:

Source File: src/index.scss

.rtad-audit-dashboard-editor,
.rtad-audit-dashboard-frontend {
    border: 1px solid #ccc;
    padding: 15px;
    margin-bottom: 15px;
    background-color: #f9f9f9;
    border-radius: 4px;

    h3 {
        margin-top: 0;
        color: #333;
    }

    table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 10px;

        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }
    }
}

In src/index.js:

  • We import necessary components from @wordpress/blocks, @wordpress/element, @wordpress/editor, and @wordpress/components.
  • wp.apiFetch is imported for making REST API requests.
  • AuditLogFetcher is a React component that extends Component.
  • componentDidMount: This lifecycle method is called after the component is mounted to the DOM. It’s the perfect place to call fetchLogs.
  • fetchLogs: This method uses apiFetch to make a GET request to our custom REST API endpoint (/rtad/v1/logs). It updates the component’s state with the fetched logs, or an error if one occurs.
  • The render method handles different states: loading (shows a spinner), error (shows an error message and a retry button), no logs found, and the actual display of logs in a <Table> component from @wordpress/components.
  • InspectorControls: This allows us to add settings to the block’s sidebar (Inspector). Here, we’ve added a PanelColorSettings to allow users to change the background color of the block.
  • attributes: We define backgroundColor as an attribute, which means its value will be saved with the post and can be controlled by the user.
  • edit(props): This function defines how the block appears in the editor. It renders the InspectorControls and the AuditLogFetcher component, passing the backgroundColor attribute down.
  • save(props): This function defines the static HTML saved to the database. For dynamic blocks that fetch data client-side, it’s common to return null, indicating that the rendering will be handled by JavaScript on the front-end or by a server-side callback. We’ve also included a commented-out example of static HTML output.

After setting up the source files, run:

npm run build

This command will generate the build/index.js and build/index.css files required by our plugin.

Real-time Updates and Considerations

The current implementation fetches logs when the block is loaded in the editor or on the front-end. For true “real-time” updates, you would typically implement one of the following:

  • Polling: Use setInterval within the AuditLogFetcher component to periodically re-fetch logs (e.g., every 30 seconds). This is the simplest approach but can be inefficient.
  • WebSockets: For more immediate updates, integrate a WebSocket solution. WordPress doesn’t have native WebSocket support, so you’d need a plugin or a separate service to handle WebSocket connections and push updates to the client.
  • Server-Sent Events (SSE): A simpler alternative to WebSockets for one-way communication from server to client. You could create another REST API endpoint that keeps the connection open and streams data.

Polling Example (within AuditLogFetcher):

// Inside AuditLogFetcher class

componentDidMount() {
    this.fetchLogs(); // Initial fetch
    this.intervalId = setInterval(() => this.fetchLogs(), 30000); // Fetch every 30 seconds
}

componentWillUnmount() {
    clearInterval(this.intervalId); // Clean up the interval when the component unmounts
}

// ... rest of the class

Security Considerations:

  • The permission_callback on the REST API route is critical. Ensure it’s robust and only allows authorized users.
  • Sanitize and validate any data you receive from the API (though in this case, we’re only sending data).
  • Be mindful of the performance impact of frequent API calls, especially on busy sites.

By following these steps, you’ve created a custom Gutenberg block that leverages custom REST API routes to display real-time audit data, complete with basic styling and user-configurable options.

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

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

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

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

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