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.