Introduction
Creating a custom WordPress block lets you extend the block editor with functionality that fits your site’s exact needs. Whether you want a unique call‑to‑action, a reusable layout component, or a dynamic display of data, building your own block gives you control without relying on third‑party plugins. This guide walks through the steps required to build a simple static block, add editable attributes, and then turn it into a dynamic block that renders content on the server.
Understanding the Block Architecture
WordPress blocks consist of two main parts: the editor script that defines how the block appears and behaves in the block editor, and the server‑side render callback that outputs the final markup on the front end. The editor script is usually written in JavaScript (or TypeScript) and compiled with tools like @wordpress/scripts. The server side can be a PHP function that returns HTML, which WordPress calls when rendering the page.
Each block is described by a block.json file. This file tells WordPress where to find the editor script, the style sheet, and the render callback. It also declares the block’s name, category, icon, and any attributes it uses.
Setting Up a Development Environment
Start with a local WordPress installation. Tools such as LocalWP, DesktopServer, or a simple LAMP stack work fine. You’ll need Node.js (≥14) and npm or yarn to manage JavaScript dependencies.
- Create a folder for your plugin inside wp-content/plugins, e.g., my-custom-block.
- Inside that folder run
npm init -yto generate a package.json. - Install the WordPress scripts package:
npm install @wordpress/scripts --save-dev. - Add the following scripts to package.json:
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
}
}
Running npm start will watch your source files and rebuild them on change, which is handy during development.
Registering the Block with block.json
Create a file named block.json in the plugin root with the following content:
{
"name": "my-plugin/alert-box",
"title": "Alert Box",
"category": "widgets",
"icon": "warning",
"description": "A simple alert box with editable text and type selection.",
"keywords": ["alert", "notice", "banner"],
"textdomain": "my-plugin",
"editorScript": "file:./build/index.js",
"style": "file:./build/style-index.css",
"attributes": {
"message": {
"type": "string",
"default": "This is an alert."
},
"type": {
"type": "string",
"default": "info",
"enum": ["info", "success", "warning", "error"]
}
},
"render": "file:./render.php"
}
The
editorScriptpoints to the compiled JavaScript that powers the block UI. Thestylehandle loads the front‑end stylesheet. Theattributesobject defines the data the block can store. Finally,rendertells WordPress which PHP file will generate the markup displayed on the site.Writing the Editor Script (index.js)
Create a src folder and inside it place index.js. This file uses the @wordpress/components and @wordpress/blocks packages to define the block’s edit and save functions.
import { registerBlockType } from '@wordpress/blocks';
import { PanelBody, TextControl, SelectControl } from '@wordpress/components';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
registerBlockType('my-plugin/alert-box', {
edit: ({ attributes, setAttributes }) => {
const { message, type } = attributes;
return (
<>
label="Message"
value={message}
onChange={(value) => setAttributes({ message: value })}
/>
label="Type"
value={type}
options={[
{ label: 'Info', value: 'info' },
{ label: 'Success', value: 'success' },
{ label: 'Warning', value: 'warning' },
{ label: 'Error', value: 'error' }
]}
onChange={(value) => setAttributes({ type: value })}
/>
{message}
>
);
},
save: ({ attributes }) => {
const { message, type } = attributes;
return (
{message}
);
}
});
The edit function returns the JSX that appears in the editor, complete with an inspector panel where users can change the message and type. The save function returns the markup that gets saved to post content. Both functions reuse the same class names so the editor preview matches the front end.
Adding Styles
Create a src/style-index.css file (or use SCSS if you prefer). Simple styling might look like this:
.alert {
padding: 12px 16px;
border-radius: 4px;
margin-bottom: 1em;
}
.alert-info { background: #e3f2fd; color: #0d47a1; border-left: 4px solid #2196f3; }
.alert-success { background: #e8f5e9; color: #1b5e20; border-left: 4px solid #4caf50; }
.alert-warning { background: #fff8e1; color: #f57f17; border-left: 4px solid #ffeb3b; }
.alert-error { background: #ffebee; color: #b71c1c; border-left: 4px solid #f44336; }
When you run
npm run build, the compiled CSS will be placed in the build folder and automatically loaded via the style handle declared in block.json.Creating the Server‑Side Render Callback
Even though the save function already outputs static HTML, many blocks benefit from a PHP render callback, especially when you need to fetch dynamic data or apply filters. Create a file named render.php in the plugin root:
function my_plugin_render_alert_box( $attributes ) {
$message = esc_html( $attributes['message'] );
$type = esc_attr( in_array( $attributes['type'], ['info', 'success', 'warning', 'error'] ) ? $attributes['type'] : 'info' );
return sprintf(
'%s',
$type,
$message
);
}
The render function receives the block’s attributes, sanitizes them, and returns the final HTML. Because the save function already outputs identical markup, the render callback simply mirrors it. This approach ensures that any future changes to the markup (for example, adding a filter) only need to be made in one place.
Enqueueing the Plugin
Finally, the main plugin file (e.g., my-custom-block.php) needs to register the block assets and tell WordPress to use the render callback.
/*
Plugin Name: My Custom Alert Block
Description: A custom alert block built with the block editor.
Version: 1.0
Author: Your Name
*/
function my_plugin_register_block() {
// Register the block using metadata from block.json.
register_block_type_from_metadata( __DIR__ );
}
add_action( 'init', 'my_plugin_register_block' );
Place this file in the same plugin folder. Activate the plugin from the WordPress admin screen, then edit a post or page. You should see the Alert Box block available in the Widgets category.
Testing and Debugging
While developing, keep the following tips in mind:
- Run
npm startin a terminal to watch for changes; the block editor will reload automatically when you save a file. - Open the browser’s developer tools to check for JavaScript errors or missing assets.
- If the block does not appear, verify that the plugin is active and that the
register_block_type_from_metadatahook is firing. - Use the
WP_DEBUGconstant in wp-config.php to see PHP warnings that might arise from the render callback.
Common Pitfalls to Avoid
Developers often encounter a few recurring issues when building their first block:
- Forgetting to rebuild the JavaScript after editing src files. The editor uses the compiled files in the build folder, so changes in src won’t take effect until you run the build script.
- Using inconsistent naming between block.json attributes and the JavaScript attributes object. A typo will cause the attribute to be undefined.
- Neglecting to sanitize output in the render callback. Even though the block’s save function escapes content, the render callback runs on every page load and must protect against malicious data.
- Over‑loading the edit function with complex logic. Keep the editor UI lightweight; heavy computations belong in the render callback or a separate API call.
Best Practices for Maintainable Blocks
As your block library grows, consider these habits:
- Separate concerns: keep the block.json metadata, the editor script, the style sheet, and the render callback in distinct files.
- Use descriptive block names that include your plugin or vendor prefix (e.g., my-plugin/alert-box) to avoid collisions with other plugins.
- Leverage the
@wordpress/i18npackage to make strings translatable if you plan to distribute the block. - Document the block’s attributes and usage in a README file inside the plugin folder; future contributors will appreciate clear guidance.
- When a block needs to fetch data from an external source, implement the fetch in the render callback and use WordPress’ HTTP API or REST client to keep the editor snappy.
Frequently Asked Questions (FAQs)
Do I need to know React or JSX to create a block?
While the block editor is built on React, you can write blocks using plain JavaScript and the @wordpress/element helpers. JSX is convenient but not required; you can use wp.element.createElement directly if you prefer.
Can I use TypeScript instead of JavaScript?
Yes. The @wordpress/scripts package supports TypeScript out of the box. Rename your src/index.js to src/index.ts, add a tsconfig.json, and the build process will transpile it to JavaScript automatically.
How do I make a block that displays dynamic content, like a list of recent posts?
Store minimal attributes in the block (for example, a number of posts to show). In the render callback, run a WP_Query based on those attributes and return the generated markup. The edit side can show a placeholder or a preview of the query.
Is it necessary to provide a separate stylesheet for the editor and the front end?
Providing both ensures the block looks similar in the editor and on the site. You can reuse the same CSS file for both handles, but sometimes you need editor‑specific overrides (e.g., to hide certain elements only in the editor).
What happens if I change the block’s attributes after it’s already used in posts?
If you rename an attribute or change its type, existing blocks will lose that data unless you provide a migration function in block.json using the attributes default and transform properties, or you handle the change gracefully in the edit and save functions.
Can I distribute my block as a standalone plugin?
Absolutely. Once the plugin folder is complete, zip it and install it on any WordPress site that meets the minimum PHP and Node.js requirements (the latter only needed for development; the final product runs on PHP alone).
How do I troubleshoot a block that appears broken in the editor but fine on the front end?
Check the browser console for JavaScript errors. Common causes include missing dependencies, incorrect paths in block.json, or using a feature that requires a newer version of WordPress than the one you are testing against.