Gutenberg replaced the “Classic Editor” (which was essentially a digital typewriter) with a modular system. Every element—a paragraph, an image, or a button—is a standalone object (a block).
The “Core” Categories:
Common Blocks: The essentials (Paragraph, Heading, Image).
Formatting: Tools for structured data like Table, Code snippets, or Pullquotes.
Layout Elements: These are “container” blocks. Columns, Groups, and Rows allow you to create complex grid structures without writing HTML/CSS from scratch.
Widgets & Embeds: Dynamic content like LatestPosts or third-party integrations like YouTube and Spotify.
// JavaScript Filter to add a class to all 'core/paragraph' blocks
function addMyCustomClass( settings, name ) {
if ( name !== 'core/paragraph' ) {
return settings;
}
return Object.assign( {}, settings, {
attributes: Object.assign( {}, settings.attributes, {
className: {
type: 'string',
default: 'my-default-class',
},
} ),
} );
}
wp.hooks.addFilter( 'blocks.registerBlockType', 'my-plugin/class-filter', addMyCustomClass );
Gutenberg is built on React and uses a Redux-like data system called @wordpress/data. This is where the editor “remembers” what you’ve typed, which blocks are selected, and the overall site settings.
State is held in Stores. The most common ones are:
core/editor: Information about the current post (title, date, content).core/block-editor: Information about the blocks (selection, hierarchy).core: General site data (site title, available taxonomies).
1. useSelect (To get data)
This hook “subscribes” to the store. If the data in the store changes, your component re-renders.
How it works: It takes a callback function that receives a
selectobject. You use this object to pick which store you want to talk to (e.g.,core,core/editor,core/block-editor) and which selector to run.The “Resolver” Benefit: One major difference in WordPress is that
useSelectcan automatically trigger Resolvers. If the data you’re asking for isn’t in the store yet, WordPress will automatically fire an API request to fetch it for you.
import { useSelect } from '@wordpress/data';
const BlockCount = () => {
const count = useSelect( ( select ) => {
return select( 'core/block-editor' ).getBlockCount();
}, [] );
return <div>Total Blocks: { count }</div>;
};
2. useDispatch (To change data)
To update the state (e.g., changing a block’s attribute or adding a new block), you “dispatch” an action.
How it works: You usually pass the name of the store you want to target (like
'core/editor') directly into the hook. It returns an object containing all the available action creators for that store.Convenience: Unlike standard Redux where you get a
dispatchfunction and have to calldispatch(action()), WordPress allows you to call the returned functions directly.
import { useDispatch } from '@wordpress/data';
const MyCustomButton = () => {
const { insertBlock } = useDispatch( 'core/block-editor' );
return (
<button onClick={ () => insertBlock( wp.blocks.createBlock( 'core/paragraph' ) ) }>
Add Paragraph
</button>
);
};
The block.json (The “ID Card”)
Previously, we defined block settings in JavaScript. Now, we use block.json. This is a massive improvement because it allows the server (PHP) to “know” about the block without having to run a heavy JavaScript file. It handles:
Permissions: What can this block do?
Discovery: What category does it belong to?
Assets: Which CSS and JS files should be loaded?
edit vs. save (The “Two Worlds”)
edit: This is the interface the user sees inside the WordPress admin. It’s dynamic, uses React hooks, and provides the controls.save: This defines the static HTML that gets saved into the database. When a visitor views your site, they aren’t running your React code; they are seeing the HTML output generated by this function.
CREATING BLOCKS
To create your first simple block.
Create a plugin.
In that plugin folder, initialize Node.js by running
npm init. This will create apackage.jsonfile.Now install the WordPress scripts as a dev dependency.
npm install @wordpress/scripts --save-devAdd the following scripts in your
package.jsonfile."start": "wp-scripts start", "build": "wp-scripts build"Create a
srcdirectory, and inside that, create a directory with your block’s name (e.g.,first-block).Now, create a block.json file for the block in that directory.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"title": "First Block",
"name": "learning/first-block",
"version": "0.1.0",
"category": "text",
"icon": "universal-access-alt",
"textdomain": "first-block",
"editorScript": "file:./index.js"
}
import { registerBlockType } from "@wordpress/blocks";
import metadata from "./block.json";
// Register the block
registerBlockType(metadata.name, {
edit: function () {
return <p> Hello world! This is my first block. (editor)</p>;
},
save: function () {
return <p> Hello World! This is my first block. (frontend) </p>;
},
});
- add in main plugin file
function register_blocks() {
register_block_type( __DIR__ . '/build/first-block' );
}
add_action( 'init', 'register_blocks' );
double register
2. The Server Side (PHP)
Location: your-plugin.php PHP needs to register the block so WordPress knows it exists before the page even loads. This allows the server to manage assets (CSS/JS) and handle things like search or the REST API.
<?php
/**
* Plugin Name: My Custom Block
*/
function my_plugin_register_block() {
// We point to the DIRECTORY containing the block.json file.
// WordPress automatically reads the JSON to register the block.
register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'my_plugin_register_block' );
Location: src/index.js JavaScript needs to register the block so the Block Editor (Gutenberg) knows how to render the UI, the toolbar, and the sidebar settings while you are typing.
import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
registerBlockType( metadata.name, {
/**
* The 'edit' function defines what the user sees
* and interacts with in the dashboard.
*/
edit: () => {
return <div className="p-4 bg-yellow-100">I am the Editor view!</div>;
},
/**
* The 'save' function defines the HTML
* pushed into the database for the frontend.
*/
save: () => {
return <div className="notice-box">I am the Frontend view!</div>;
},
} );
why register twice?
Server-Side Rendering (SSR): If your block needs to fetch data dynamically (like “Latest Posts”), PHP needs to render that HTML on the server before the page loads.
The REST API: WordPress needs to tell external apps what blocks are available. PHP handles this communication.
Content Parsing: When WordPress saves a post, it saves it as HTML comments. PHP parses these comments to understand the block structure for search and indexing.
Can we do it only on JS
Yes, but you shouldn’t. Technically, you can call registerBlockType in JavaScript without the PHP counterpart. However:
Visibility: The block won’t appear in the REST API, meaning other apps or even the WordPress Query Loop block might not “see” it.
Performance: Without PHP registration, WordPress doesn’t know which CSS/JS files to load until the editor starts running.
Implicit Enqueuing: When you define "editorScript": "file:./index.js" in your block.json, WordPress automatically handles the enqueuing.
When you run npm run build, a file named index.asset.php is generated. This file is a small PHP array containing two vital pieces of data:
Dependencies: It lists every WordPress package your block uses (e.g.,
wp-blocks,wp-element,wp-i18n). WordPress uses this to ensure these scripts load before your block runs.Version: It creates a unique hash (like
a1b2c3d4) based on the file content.
When you use registerBlockStyle( 'block-name', array( 'name' => 'xyz', ... ) ), WordPress follows a strict CSS naming convention to ensure consistency.
The wrapper of your block will automatically receive a class formatted as: is-style-{name}
So, if your style name is xyz, the class assigned to the block wrapper will be: is-style-xyz
FORMAT API
The Format API is a specific part of the WordPress Block Editor (Gutenberg) ecosystem. It allows developers to create inline formatting options for the RichText component—essentially adding new buttons to the toolbar that pops up when you highlight text (like Bold, Italic, or custom markers).
To add a custom format, you use the registerFormatType function from the @wordpress/rich-text package. It requires three main components:
Name: A unique string (e.g.,
my-plugin/custom-highlight).Tag: The HTML element used to wrap the text (e.g.,
<span>,<mark>, or<code>).Attributes: Any CSS classes or inline styles applied to that tag.
import { registerFormatType } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
registerFormatType( 'my-custom-format/sample', {
title: 'Sample Output',
tagName: 'samp',
className: 'sample-output',
edit: ( { isActive, value, onChange } ) => {
return (
<RichTextToolbarButton
icon="editor-code"
title="Sample Output"
onClick={ () => {
onChange( toggleFormat( value, { type: 'my-custom-format/sample' } ) );
} }
isActive={ isActive }
/>
);
},
} );