• 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 database optimizer portal block for Gutenberg using REST API custom routes

Step-by-Step Guide to building a custom database optimizer portal block for Gutenberg using REST API custom routes

Setting Up the WordPress REST API for Custom Data Access

To build a dynamic Gutenberg block that interacts with a custom database optimization portal, we first need to expose our optimization data via the WordPress REST API. This involves creating custom endpoints that our block can query. We’ll leverage WordPress’s built-in REST API registration functions to achieve this.

The core of this process lies in the `register_rest_route` function. This function allows us to define new API endpoints, specify the HTTP methods they respond to (GET, POST, etc.), and associate callback functions that will handle the requests and return data. For our database optimizer portal, we’ll likely need endpoints to fetch optimization statistics, retrieve specific optimization tasks, and potentially trigger new optimizations.

Registering a Custom REST API Endpoint

Let’s start by creating a simple endpoint to retrieve a list of available optimization tasks. This code should be placed within your plugin’s main PHP file or an included file.

<?php
/**
 * Register custom REST API routes for the optimizer portal.
 */
function register_optimizer_api_routes() {
    register_rest_route( 'optimizer/v1', '/tasks', array(
        'methods'  => 'GET',
        'callback' => 'get_optimizer_tasks',
        'permission_callback' => function () {
            // Basic permission check: ensure user is logged in and has sufficient capabilities.
            // In a production environment, you'd want more robust checks.
            return current_user_can( 'manage_options' );
        },
    ) );

    register_rest_route( 'optimizer/v1', '/task/(?P<id>\d+)', array(
        'methods'  => 'GET',
        'callback' => 'get_optimizer_task_details',
        'permission_callback' => function () {
            return current_user_can( 'manage_options' );
        },
        'args' => array(
            'id' => array(
                'validate_callback' => function( $param, $request, $key ) {
                    return is_numeric( $param );
                }
            ),
        ),
    ) );

    // Add more routes here for POST, PUT, DELETE operations as needed.
}
add_action( 'rest_api_init', 'register_optimizer_api_routes' );

/**
 * Callback function to retrieve optimizer tasks.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST | WP_Error Response object on success, or WP_Error object on failure.
 */
function get_optimizer_tasks( WP_REST_Request $request ) {
    // In a real-world scenario, this would query your custom database tables
    // or a more complex data source for optimization tasks.
    $tasks = array(
        array( 'id' => 1, 'name' => 'Optimize Image Sizes', 'status' => 'completed', 'last_run' => '2023-10-27 10:00:00' ),
        array( 'id' => 2, 'name' => 'Clean Up Database Transients', 'status' => 'pending', 'last_run' => null ),
        array( 'id' => 3, 'name' => 'Minify CSS/JS', 'status' => 'running', 'last_run' => '2023-10-27 09:30:00' ),
    );

    return new WP_REST_Response( $tasks, 200 );
}

/**
 * Callback function to retrieve details for a specific optimizer task.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST | WP_Error Response object on success, or WP_Error object on failure.
 */
function get_optimizer_task_details( WP_REST_Request $request ) {
    $task_id = $request['id'];

    // Fetch specific task details based on $task_id.
    // This is a placeholder. Replace with actual data retrieval logic.
    $task_details = array(
        'id' => $task_id,
        'name' => "Task {$task_id} Details",
        'description' => 'Detailed information about optimization task ' . $task_id . '.',
        'progress' => rand(0, 100) . '%', // Example dynamic data
        'last_run_log' => 'Log entry 1...\nLog entry 2...',
    );

    if ( empty( $task_details ) ) {
        return new WP_Error( 'optimizer_task_not_found', 'Optimizer task not found', array( 'status' => 404 ) );
    }

    return new WP_REST_Response( $task_details, 200 );
}
?>

In this snippet:

  • We hook into the rest_api_init action to register our routes.
  • register_rest_route is used to define the endpoint /optimizer/v1/tasks for GET requests. The first argument, 'optimizer/v1', is the namespace.
  • The callback parameter points to the PHP function (get_optimizer_tasks) that will execute when this endpoint is hit.
  • A basic permission_callback is included to ensure only users with the ‘manage_options’ capability can access these endpoints. This is crucial for security.
  • We also define a route for individual task details, using a regex parameter (?P<id>\d+) to capture the task ID.
  • The callback functions (get_optimizer_tasks and get_optimizer_task_details) simulate fetching data. In a production system, these would interact with your custom database tables or other data sources.
  • WP_REST_Response is used to return data in a structured JSON format, along with an appropriate HTTP status code.
  • WP_Error is used to signal issues, such as a task not being found.

Developing the Gutenberg Block for the Optimizer Portal

Now that our API endpoints are ready, we can build the Gutenberg block. This block will serve as the user interface within the WordPress editor for interacting with our optimizer portal. We’ll use JavaScript (specifically React, as used by Gutenberg) to fetch data from our custom REST API endpoints and display it dynamically.

Block Registration and Editor Script

First, we need to register our block type and enqueue the necessary JavaScript and CSS files for the editor. This is typically done in your plugin’s main PHP file.

<?php
/**
 * Enqueue block editor assets.
 */
function enqueue_optimizer_block_assets() {
    // Register the block script.
    wp_register_script(
        'optimizer-block-editor-script',
        plugin_dir_url( __FILE__ ) . 'build/index.js', // Path to your compiled JS
        array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-api-fetch' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
    );

    // Register the block style.
    wp_register_style(
        'optimizer-block-editor-style',
        plugin_dir_url( __FILE__ ) . 'build/index.css', // Path to your compiled CSS
        array( 'wp-edit-blocks' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' )
    );

    // Register the block.
    register_block_type( 'optimizer/portal', array(
        'editor_script' => 'optimizer-block-editor-script',
        'editor_style'  => 'optimizer-block-editor-style',
        'render_callback' => 'render_optimizer_block_frontend', // For frontend rendering
    ) );
}
add_action( 'init', 'enqueue_optimizer_block_assets' );

/**
 * Server-side rendering callback for the block.
 * This is optional if your block is fully dynamic via JS, but good for SEO and initial load.
 *
 * @param array $attributes Block attributes.
 * @return string HTML output.
 */
function render_optimizer_block_frontend( $attributes ) {
    // This function would typically fetch data server-side or render a static placeholder.
    // For a fully dynamic block, it might just return an empty div or a loading indicator.
    return '<div class="optimizer-portal-frontend">Loading Optimizer Data...</div>';
}
?>

Explanation:

  • wp_register_script and wp_register_style are used to enqueue our block’s assets. The dependencies include wp-api-fetch, which is essential for making requests to the WordPress REST API from the client-side JavaScript.
  • register_block_type registers our block with the name 'optimizer/portal'.
  • editor_script and editor_style point to our registered assets for the block editor.
  • render_callback is defined for server-side rendering. While our block will be largely dynamic via JavaScript, a server-side render can provide initial content or be used for static blocks.

Client-Side JavaScript (React) for the Block

This is where the magic happens. We’ll use React components to build the block’s UI in the editor. The wp.apiFetch utility will be used to query our custom REST API endpoints.

Assuming you have a build process set up (e.g., using `@wordpress/scripts` or a custom Webpack configuration) that compiles your React/JSX code into build/index.js and build/index.css.

// src/index.js (or your main block JS file)

const { registerBlockType } = wp.blocks;
const { Component, Fragment } = wp.element;
const { InspectorControls, RichText } = wp.editor;
const { PanelBody, Button, TextControl, Spinner, Table, TableRow, TableCell, TableHead, TableBody } = wp.components;
const apiFetch = wp.apiFetch;

// Define the block attributes
const blockAttributes = {
    title: {
        type: 'string',
        default: 'Database Optimizer Status',
    },
};

class OptimizerPortalBlock extends Component {
    constructor(props) {
        super(props);
        this.state = {
            tasks: [],
            loading: true,
            error: null,
            selectedTaskId: null,
            taskDetails: null,
            detailsLoading: false,
        };
    }

    componentDidMount() {
        this.fetchTasks();
    }

    fetchTasks() {
        this.setState({ loading: true, error: null });
        apiFetch({ path: '/optimizer/v1/tasks' })
            .then(tasks => {
                this.setState({ tasks, loading: false });
            })
            .catch(error => {
                console.error('Error fetching tasks:', error);
                this.setState({ error: error.message || 'Failed to load tasks.', loading: false });
            });
    }

    fetchTaskDetails(taskId) {
        this.setState({ selectedTaskId: taskId, taskDetails: null, detailsLoading: true, error: null });
        apiFetch({ path: `/optimizer/v1/task/${taskId}` })
            .then(details => {
                this.setState({ taskDetails: details, detailsLoading: false });
            })
            .catch(error => {
                console.error(`Error fetching task ${taskId} details:`, error);
                this.setState({ error: error.message || `Failed to load details for task ${taskId}.`, detailsLoading: false });
            });
    }

    handleTaskClick(taskId) {
        if (this.state.selectedTaskId === taskId) {
            // Collapse if already selected
            this.setState({ selectedTaskId: null, taskDetails: null });
        } else {
            this.fetchTaskDetails(taskId);
        }
    }

    render() {
        const { attributes, setAttributes } = this.props;
        const { title } = attributes;
        const { tasks, loading, error, selectedTaskId, taskDetails, detailsLoading } = this.state;

        return (
            <Fragment>
                <InspectorControls>
                    <PanelBody title="Block Settings">
                        <TextControl
                            label="Block Title"
                            value={ title }
                            onChange={ ( newTitle ) => setAttributes( { title: newTitle } ) }
                        />
                        <Button isPrimary onClick={ () => this.fetchTasks() }>
                            Refresh Tasks
                        </Button>
                    </PanelBody>
                </InspectorControls>

                <div className="optimizer-portal-block">
                    <RichText
                        tagName="h2"
                        value={ title }
                        onChange={ ( newTitle ) => setAttributes( { title: newTitle } ) }
                        placeholder="Enter block title..."
                    />

                    {loading && <Spinner />}
                    {error && <p style={{ color: 'red' }}>{error}</p>}

                    {!loading && !error && tasks.length === 0 && (
                        <p>No optimization tasks found.</p>
                    )}

                    {!loading && !error && tasks.length > 0 && (
                        <Table>
                            <TableHead>
                                <TableRow>
                                    <TableCell>Task Name</TableCell>
                                    <TableCell>Status</TableCell>
                                    <TableCell>Last Run</TableCell>
                                </TableRow>
                            </TableHead>
                            <TableBody>
                                {tasks.map(task => (
                                    <Fragment key={ task.id }>
                                        <TableRow
                                            className={ `task-row ${selectedTaskId === task.id ? 'selected' : ''}` }
                                            onClick={ () => this.handleTaskClick(task.id) }
                                            style={{ cursor: 'pointer' }}
                                        >
                                            <TableCell>{task.name}</TableCell>
                                            <TableCell>{task.status}</TableCell>
                                            <TableCell>{task.last_run || 'Never'}</TableCell>
                                        </TableRow>
                                        {selectedTaskId === task.id && (
                                            <TableRow>
                                                <TableCell colSpan="3">
                                                    {detailsLoading && <Spinner />}
                                                    {taskDetails && !detailsLoading && (
                                                        <div className="task-details">
                                                            <h4>Details for {task.name}</h4>
                                                            <p><strong>Progress:</strong> {taskDetails.progress}</p>
                                                            <pre>{taskDetails.last_run_log}</pre>
                                                        </div>
                                                    )}
                                                    {!taskDetails && !detailsLoading && !error && (
                                                        <p>Click a task to view details.</p>
                                                    )}
                                                </TableCell>
                                            </TableRow>
                                        )}
                                    </Fragment>
                                ))}
                            </TableBody>
                        </Table>
                    )}
                </div>
            </Fragment>
        );
    }
}

// Register the block
registerBlockType( 'optimizer/portal', {
    title: 'Optimizer Portal',
    icon: 'performance', // Choose an appropriate Dashicon
    category: 'widgets', // Or 'common', 'layout', etc.
    attributes: blockAttributes,
    edit: OptimizerPortalBlock,
    save: () => {
        // The save function should return null for dynamic blocks
        // or the static HTML that will be rendered server-side.
        // Since we have a render_callback, we can return null here.
        return null;
    },
} );

Key aspects of the JavaScript code:

  • We import necessary components from @wordpress/blocks, @wordpress/element, @wordpress/editor, @wordpress/components, and @wordpress/api-fetch.
  • The OptimizerPortalBlock class is a React component that extends Component.
  • componentDidMount is used to fetch the initial list of tasks when the block is loaded in the editor.
  • apiFetch({ path: '/optimizer/v1/tasks' }) makes a GET request to our custom REST API endpoint.
  • The state manages the tasks, loading status, and any potential error.
  • We use InspectorControls to add settings to the block sidebar, like a title input and a refresh button.
  • The main block area displays the title (using RichText for inline editing) and a table of tasks.
  • Clicking a task row triggers fetchTaskDetails to load more information for that specific task via the /optimizer/v1/task/:id endpoint.
  • The save function returns null because this is a dynamic block; its content will be rendered on the frontend by the render_callback defined in PHP.

Styling the Block

You’ll also need some CSS for both the editor and the frontend. Create a src/style.scss (or similar) and import it into your build process.

/* src/style.scss */

.optimizer-portal-block {
    border: 1px solid #ddd;
    padding: 15px;
    background-color: #f9f9f9;
    border-radius: 4px;

    h2 {
        margin-top: 0;
        font-size: 1.5em;
        color: #333;
    }

    .task-row {
        transition: background-color 0.2s ease;
        &:hover {
            background-color: #eef;
        }
        &.selected {
            background-color: #dde;
            font-weight: bold;
        }
    }

    .task-details {
        padding: 10px;
        background-color: #fff;
        border-top: 1px solid #eee;
        margin-top: 5px;

        h4 {
            margin-top: 0;
            margin-bottom: 10px;
            color: #555;
        }

        pre {
            white-space: pre-wrap; /* Allows text to wrap */
            word-wrap: break-word; /* Breaks long words */
            background-color: #f0f0f0;
            padding: 8px;
            border-radius: 3px;
            font-size: 0.9em;
            max-height: 150px;
            overflow-y: auto;
        }
    }
}

/* Frontend specific styles if needed */
.optimizer-portal-frontend {
    /* Styles for the block on the actual website */
    border: 1px solid #ccc;
    padding: 20px;
    background-color: #f0f0f0;
}

Ensure your build process (e.g., Webpack) is configured to compile this SCSS into build/index.css and that the enqueue_optimizer_block_assets function correctly points to it.

Deployment and Testing

To deploy this solution:

  • Place the PHP code in your plugin’s main file or an included file.
  • Organize your JavaScript and SCSS files within a src directory.
  • Set up a build process (e.g., using @wordpress/scripts) to compile your src assets into a build directory. Run npm run build or yarn build.
  • Activate your plugin in WordPress.
  • Navigate to the WordPress editor (for a post or page) and add the “Optimizer Portal” block.
  • Verify that the tasks are loaded and that clicking on them displays details.
  • Test the REST API endpoints directly using tools like Postman or by navigating to your-site.com/wp-json/optimizer/v1/tasks in your browser (you’ll need to be logged in with sufficient permissions).

Advanced Considerations for Production

For a production-ready system, consider the following:

  • Robust Error Handling: Implement more detailed error logging and user-friendly error messages on both the client and server sides.
  • Security: The current permission callback is basic. Implement granular capabilities and nonce verification for POST/PUT/DELETE requests. Sanitize all input data rigorously.
  • Data Source: Replace the placeholder data arrays with actual database queries to your custom tables or optimized data stores. Use WordPress’s DB abstraction layer ($wpdb) for security and portability.
  • Performance: For large datasets, implement pagination for the REST API endpoints. Cache API responses where appropriate. Optimize database queries.
  • Frontend Rendering: While the block is dynamic via JS, consider if some data could be pre-rendered server-side for better initial load performance and SEO.
  • Internationalization (i18n): Use WordPress i18n functions (__, _e, etc.) for all user-facing strings in both PHP and JavaScript.
  • Build Process: A robust build process is essential for managing dependencies, transpiling modern JavaScript, and optimizing assets.

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 to building a custom Elasticsearch search bar block for Gutenberg using React components
  • Troubleshooting guide: Resolving memory leak spikes caused by unclosed custom database loops in customer support tickets
  • Optimizing p99 database query response latency in multi-site Domain-driven architecture (DDD) blocks custom tables
  • How to design a modular Action-hook Event Mediator architecture for enterprise-level custom plugins
  • Step-by-Step Guide to building a custom database optimizer portal block for Gutenberg using Next.js headless configurations

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 (41)
  • 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 (67)
  • WordPress Plugin Development (73)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Step-by-Step Guide to building a custom Elasticsearch search bar block for Gutenberg using React components
  • Troubleshooting guide: Resolving memory leak spikes caused by unclosed custom database loops in customer support tickets
  • Optimizing p99 database query response latency in multi-site Domain-driven architecture (DDD) blocks custom tables

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