• 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 custom charts dashboard block for Gutenberg using React components

Step-by-Step Guide to building a custom custom charts dashboard block for Gutenberg using React components

Project Setup and Environment Configuration

Building a custom Gutenberg block for WordPress, especially one involving dynamic data visualization like charts, necessitates a robust development environment. We’ll leverage Node.js, npm/yarn, and the WordPress `@wordpress/scripts` package for compilation and asset management. This approach ensures compatibility with the Gutenberg editor and efficient handling of React components.

First, ensure you have Node.js and npm (or yarn) installed. You can download them from nodejs.org. Next, navigate to your WordPress plugin’s directory (or create a new one) and initialize a `package.json` file if one doesn’t exist:

cd wp-content/plugins/your-plugin-slug
npm init -y

Now, install the necessary development dependencies. The `@wordpress/scripts` package provides build tools for JavaScript, CSS, and React, including Webpack and Babel configurations tailored for Gutenberg development.

npm install @wordpress/scripts --save-dev
# or
yarn add @wordpress/scripts --dev

Configure your `package.json` to use the `@wordpress/scripts` build commands. Add the following `scripts` section:

{
  "name": "your-plugin-slug",
  "version": "1.0.0",
  "description": "Custom Charts Block for Gutenberg",
  "main": "index.js",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "keywords": ["wordpress", "gutenberg", "react", "charts"],
  "author": "Your Name",
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@wordpress/scripts": "^27.0.0"
  }
}

The `build` script will compile your React components and JavaScript into production-ready assets, while `start` will enable watch mode for faster development cycles, recompiling on file changes.

Gutenberg Block Registration and PHP Backend

Every Gutenberg block requires registration on the WordPress side. This is typically done in your plugin’s main PHP file. We’ll register an `editor_script` to load our React components in the editor and a `script` to load any necessary frontend JavaScript.

Create a main plugin file (e.g., `your-plugin-slug.php`) and add the following PHP code:

<?php
/**
 * Plugin Name: Custom Charts Block
 * Description: A custom Gutenberg block for displaying charts.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 * Text Domain: custom-charts-block
 */

function custom_charts_block_register_block() {
    // Automatically load dependencies and version
    $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

    wp_register_script(
        'custom-charts-block-editor-script',
        plugins_url( 'build/index.js', __FILE__ ),
        $asset_file['dependencies'],
        $asset_file['version']
    );

    wp_register_script(
        'custom-charts-block-frontend-script',
        plugins_url( 'build/frontend.js', __FILE__ ),
        array('chartjs'), // Example: assuming Chart.js is enqueued elsewhere or you enqueue it here
        $asset_file['version']
    );

    register_block_type( 'custom-charts-block/charts', array(
        'editor_script' => 'custom-charts-block-editor-script',
        'script'        => 'custom-charts-block-frontend-script',
        // Optional: Add render_callback for server-side rendering if needed
        // 'render_callback' => 'custom_charts_block_render_callback',
    ) );
}
add_action( 'init', 'custom_charts_block_register_block' );

// Optional: Example render_callback function if you need server-side rendering
/*
function custom_charts_block_render_callback( $attributes ) {
    // Logic to fetch data and render chart HTML server-side
    // This is more complex and often unnecessary if charts are client-side rendered
    return '<div class="custom-charts-block-wrapper">Chart Placeholder</div>';
}
*/

The `plugin_dir_path(__FILE__) . ‘build/index.asset.php’` is crucial. The `@wordpress/scripts` package automatically generates this file during the build process. It contains an array of script dependencies (like React, wp.element, wp.blocks) and a version number, ensuring proper loading and cache busting.

React Component Development for the Editor

The core of our Gutenberg block will be a React component. This component will handle the block’s UI in the editor, allowing users to configure chart type, data sources, and appearance. We’ll use the `@wordpress/components` and `@wordpress/block-editor` packages for UI elements and editor integration.

Create a `src` directory in your plugin’s root and place your React components there. Start with `src/index.js` for the block’s main registration and `src/edit.js` for the editor component.

`src/index.js`

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import metadata from '../block.json'; // Assuming you have a block.json

// Import styles if needed
// import './editor.scss';
// import './style.scss';

registerBlockType( metadata.name, {
    edit: Edit,
    // save: Save, // Define a Save component if you need static HTML output
} );

`src/edit.js`

import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl, TextControl } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import Chart from 'chart.js/auto'; // Assuming Chart.js is installed

const Edit = ( { attributes, setAttributes } ) => {
    const blockProps = useBlockProps();
    const { chartType, chartData, chartOptions } = attributes;

    // State for local chart instance to manage updates
    const [ chartInstance, setChartInstance ] = useState( null );
    const chartRef = React.useRef( null );

    useEffect( () => {
        if ( chartRef.current ) {
            const ctx = chartRef.current.getContext( '2d' );
            if ( chartInstance ) {
                chartInstance.destroy(); // Destroy previous instance
            }
            const newChartInstance = new Chart( ctx, {
                type: chartType || 'bar', // Default to 'bar'
                data: chartData || { labels: [], datasets: [] }, // Default empty data
                options: chartOptions || {}, // Default empty options
            } );
            setChartInstance( newChartInstance );
        }

        // Cleanup on unmount
        return () => {
            if ( chartInstance ) {
                chartInstance.destroy();
            }
        };
    }, [ chartType, chartData, chartOptions ] ); // Re-render chart when attributes change

    const handleChartTypeChange = ( newType ) => {
        setAttributes( { chartType: newType } );
    };

    const handleChartDataChange = ( newData ) => {
        // Basic JSON parsing for data input
        try {
            const parsedData = JSON.parse( newData );
            setAttributes( { chartData: parsedData } );
        } catch ( e ) {
            console.error( 'Invalid JSON for chart data:', e );
            // Optionally provide user feedback for invalid JSON
        }
    };

    return (
        <>
            <InspectorControls>
                <PanelBody title={ __( 'Chart Settings', 'custom-charts-block' ) }>
                    <SelectControl
                        label={ __( 'Chart Type', 'custom-charts-block' ) }
                        value={ chartType }
                        options={ [
                            { label: __( 'Bar', 'custom-charts-block' ), value: 'bar' },
                            { label: __( 'Line', 'custom-charts-block' ), value: 'line' },
                            { label: __( 'Pie', 'custom-charts-block' ), value: 'pie' },
                            { label: __( 'Doughnut', 'custom-charts-block' ), value: 'doughnut' },
                        ] }
                        onChange={ handleChartTypeChange }
                    />
                    <TextControl
                        label={ __( 'Chart Data (JSON)', 'custom-charts-block' ) }
                        value={ JSON.stringify( chartData || {} ) }
                        onChange={ handleChartDataChange }
                        help={ __( 'Enter chart data in JSON format (e.g., {"labels": ["A", "B"], "datasets": [{"label": "My Data", "data": [10, 20]}]})', 'custom-charts-block' ) }
                        textarea={ true }
                    />
                    { /* Add more controls for chart options, colors, etc. */ }
                </PanelBody>
            </InspectorControls>
            <div { ...blockProps }>
                <canvas ref={ chartRef }></canvas>
            </div>
        </>
    );
};

export default Edit;

To use Chart.js, you’ll need to install it:

npm install chart.js react-chartjs-2 --save
# or
yarn add chart.js react-chartjs-2

Note: `react-chartjs-2` is a wrapper for Chart.js that can simplify integration, but for direct canvas manipulation as shown above, `chart.js/auto` is sufficient. The example uses direct canvas manipulation for clarity on the rendering lifecycle.

Block Attributes and Data Handling

Block attributes define the data that a block stores. These are declared in a `block.json` file, which is the modern standard for Gutenberg block configuration. This file also specifies dependencies and other metadata.

Create a `block.json` file in your plugin’s root directory:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "custom-charts-block/charts",
  "version": "0.1.0",
  "title": "Custom Charts",
  "category": "widgets",
  "icon": "chart-bar",
  "description": "Display dynamic charts in your posts and pages.",
  "keywords": ["chart", "graph", "visualization", "data"],
  "attributes": {
    "chartType": {
      "type": "string",
      "default": "bar"
    },
    "chartData": {
      "type": "object",
      "default": {
        "labels": ["January", "February", "March", "April", "May", "June", "July"],
        "datasets": [
          {
            "label": "Sales",
            "data": [65, 59, 80, 81, 56, 55, 40],
            "backgroundColor": "rgba(75, 192, 192, 0.2)",
            "borderColor": "rgba(75, 192, 192, 1)",
            "borderWidth": 1
          }
        ]
      }
    },
    "chartOptions": {
      "type": "object",
      "default": {
        "responsive": true,
        "plugins": {
          "legend": {
            "position": "top"
          },
          "title": {
            "display": true,
            "text": "Monthly Sales Data"
          }
        }
      }
    }
  },
  "textdomain": "custom-charts-block",
  "editorScript": "file:./build/index.js",
  "editorStyle": "file:./build/index.css",
  "style": "file:./build/style.css",
  "viewScript": "file:./build/frontend.js"
}

The `attributes` section defines the structure and default values for our chart’s type, data, and options. The `editorScript` points to the compiled JavaScript for the editor, and `viewScript` points to the script that will run on the frontend.

Frontend Rendering and Data Fetching

For the frontend, we need a script that initializes the chart using the saved attributes. If your chart data is dynamic and needs to be fetched from the WordPress REST API or a custom endpoint, this is where that logic would reside. For simplicity, this example assumes the data is stored directly in attributes or can be fetched via a simple AJAX call.

Create `src/frontend.js`:

import Chart from 'chart.js/auto';

document.addEventListener( 'DOMContentLoaded', () => {
    const chartContainers = document.querySelectorAll( '.wp-block-custom-charts-block-charts' ); // Selector based on block name

    chartContainers.forEach( ( container ) => {
        const canvas = container.querySelector( 'canvas' );
        if ( ! canvas ) return;

        const chartDataAttribute = container.dataset.chartData; // Data attributes are prefixed with 'data-'
        const chartTypeAttribute = container.dataset.chartType;
        const chartOptionsAttribute = container.dataset.chartOptions;

        let chartData = {};
        let chartType = 'bar';
        let chartOptions = {};

        try {
            if ( chartDataAttribute ) {
                chartData = JSON.parse( chartDataAttribute );
            } else {
                // Fallback to default if attribute is missing (shouldn't happen with block.json defaults)
                chartData = { labels: [], datasets: [] };
            }

            if ( chartTypeAttribute ) {
                chartType = chartTypeAttribute;
            }

            if ( chartOptionsAttribute ) {
                chartOptions = JSON.parse( chartOptionsAttribute );
            } else {
                chartOptions = { responsive: true }; // Basic default
            }

        } catch ( e ) {
            console.error( 'Error parsing chart attributes:', e );
            return; // Stop if data is malformed
        }

        const ctx = canvas.getContext( '2d' );
        new Chart( ctx, {
            type: chartType,
            data: chartData,
            options: chartOptions,
        } );
    } );
} );

To ensure the attributes are available as data attributes on the frontend wrapper element, you need to modify the `save` function in your block’s React component (or use `render_callback` in PHP). If you’re not using a `save` function, Gutenberg will render the block’s content as defined by the `edit` component’s structure, and you’ll need to ensure the attributes are serialized correctly.

A common pattern is to use a `save` component that outputs static HTML, or to rely on `render_callback` for server-side generation. For dynamic client-side charts, you’ll want the `save` component to output a canvas element and potentially data attributes.

Let’s add a `save` component to `src/index.js` and define it in `src/save.js`:

`src/save.js`

import { useBlockProps } from '@wordpress/block-editor';

const Save = ( { attributes } ) => {
    const blockProps = useBlockProps.save( {
        // Add data attributes for frontend script to consume
        'data-chart-type': attributes.chartType,
        'data-chart-data': JSON.stringify( attributes.chartData ),
        'data-chart-options': JSON.stringify( attributes.chartOptions ),
    } );

    return (
        <div { ...blockProps }>
            <canvas></canvas>
        </div>
    );
};

export default Save;

And update `src/index.js`:

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import Save from './save'; // Import the Save component
import metadata from '../block.json';

registerBlockType( metadata.name, {
    edit: Edit,
    save: Save, // Assign the Save component
} );

Building and Deployment

Once your development is complete, run the build command to compile your assets:

npm run build
# or
yarn build

This command will generate the `build/index.js`, `build/index.asset.php`, `build/style.css`, and `build/frontend.js` files. Ensure these are included in your plugin’s deployment package. The `index.asset.php` is critical for WordPress to correctly enqueue your script with its dependencies.

For production environments, you might want to optimize assets further. The `@wordpress/scripts` package handles minification and other optimizations by default in the `build` process.

Advanced Considerations: Data Sources and Interactivity

For enterprise-level applications, charts often need to display data fetched dynamically. This can be achieved by:

  • WordPress REST API: Register custom endpoints in PHP to serve chart data. Your `frontend.js` can then use `fetch` or `wp.apiFetch` to retrieve this data before initializing the chart.
  • External APIs: If your data resides in an external service, fetch it directly from `frontend.js` (ensure CORS policies are handled).
  • Server-Side Rendering (SSR): For SEO or performance-critical applications, implement a PHP `render_callback` in `register_block_type`. This callback would fetch data and generate the chart’s initial HTML structure on the server. However, for interactive charts, client-side rendering is usually preferred.
  • Real-time Updates: For live dashboards, consider WebSockets or server-sent events to push data updates to the client, triggering chart re-renders.

When fetching data via AJAX in `frontend.js`, ensure you handle loading states and potential errors gracefully. The `wp.apiFetch` utility is recommended for interacting with the WordPress REST API as it respects WordPress’s nonce security.

Example using `wp.apiFetch` in `frontend.js` (replace with your actual endpoint):

// Inside the DOMContentLoaded listener, before chart initialization:

// Example: Fetching data from a custom REST API endpoint
const API_ENDPOINT = '/wp-json/your-plugin/v1/chart-data';

wp.apiFetch( { path: API_ENDPOINT } ).then( ( response ) => {
    if ( response && response.data ) {
        chartData = response.data; // Assuming response.data contains the chart data structure
        // Now initialize the chart with fetched data
        const ctx = canvas.getContext( '2d' );
        new Chart( ctx, {
            type: chartType,
            data: chartData,
            options: chartOptions,
        } );
    } else {
        console.error( 'Invalid response from chart data API' );
    }
} ).catch( ( error ) => {
    console.error( 'Error fetching chart data:', error );
} );

This comprehensive approach provides a solid foundation for building sophisticated, data-driven Gutenberg blocks, enabling richer content creation and dynamic dashboards within WordPress.

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

  • Implementing automated compliance reporting for custom affiliate click tracking logs ledgers using custom PHP-Spreadsheet exports
  • Troubleshooting WP_DEBUG notice floods in production when using modern Understrap styling structures wrappers
  • How to build custom WooCommerce core overrides extensions utilizing modern Metadata API (add_post_meta) schemas
  • How to securely integrate Firebase Realtime DB endpoints into WordPress custom plugins using Filesystem API
  • Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using REST API custom routes

Categories

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

Recent Posts

  • Implementing automated compliance reporting for custom affiliate click tracking logs ledgers using custom PHP-Spreadsheet exports
  • Troubleshooting WP_DEBUG notice floods in production when using modern Understrap styling structures wrappers
  • How to build custom WooCommerce core overrides extensions utilizing modern Metadata API (add_post_meta) schemas

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (849)
  • Debugging & Troubleshooting (643)
  • Security & Compliance (623)
  • 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