Build WordPress plugins from scratch: actions and filters, shortcodes, settings pages, custom post types, meta boxes, AJAX, REST endpoints, custom tables, cron and blocks.
Lessons
A block is the modern unit of content in WordPress — defined by a block.json file, edited with a React component in the editor and, for dynamic blocks, rendered on the front end by a PHP template.
You need Node.js installed. From your plugin's blocks folder:
cd wp-content/plugins/gdc-books
npx @wordpress/create-block@latest book-list --variant=dynamic --no-plugin
cd book-list
npm start # Rebuilds automatically while you edit.--variant=dynamic creates a block whose output is rendered by PHP on every page view, which is right for a list of books that changes as books are added. --no-plugin adds the block to your existing plugin instead of creating a new one.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "gdc-books/book-list",
"title": "Book List",
"category": "widgets",
"icon": "book",
"description": "Shows the latest books from the catalogue.",
"textdomain": "gdc-books",
"attributes": {
"count": { "type": "number", "default": 3 },
"showIsbn": { "type": "boolean", "default": false }
},
"supports": {
"html": false,
"align": [ "wide", "full" ],
"color": { "background": true, "text": true },
"spacing": { "padding": true }
},
"editorScript": "file:./index.js",
"style": "file:./style-index.css",
"render": "file:./render.php"
}attributes are the block's settings, stored in the post. supports switches on core design tools — colour, alignment, spacing — without you writing any UI for them.
<?php
function gdc_books_register_blocks() {
register_block_type( GDC_BOOKS_PATH . 'blocks/book-list/build' );
}
add_action( 'init', 'gdc_books_register_blocks' );One call. WordPress reads block.json from the build folder and registers the scripts, styles and render template it lists.
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, ToggleControl } from '@wordpress/components';
import ServerSideRender from '@wordpress/server-side-render';
export default function Edit( { attributes, setAttributes } ) {
const { count, showIsbn } = attributes;
return (
<div { ...useBlockProps() }>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'gdc-books' ) }>
<RangeControl
label={ __( 'Number of books', 'gdc-books' ) }
value={ count }
onChange={ ( value ) => setAttributes( { count: value } ) }
min={ 1 }
max={ 12 }
/>
<ToggleControl
label={ __( 'Show ISBN', 'gdc-books' ) }
checked={ showIsbn }
onChange={ ( value ) => setAttributes( { showIsbn: value } ) }
/>
</PanelBody>
</InspectorControls>
<ServerSideRender block="gdc-books/book-list" attributes={ attributes } />
</div>
);
}The settings appear in the block sidebar. ServerSideRender shows a live preview produced by the same PHP that renders the front end.
<?php
/**
* Renders the Book List block.
*
* @var array $attributes Block attributes.
* @var string $content Inner block content.
* @var WP_Block $block The block instance.
*/
$books = get_posts(
array(
'post_type' => 'gdc_book',
'numberposts' => min( 12, max( 1, (int) $attributes['count'] ) ),
)
);
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php if ( $books ) : ?>
<ul class="gdc-book-list">
<?php foreach ( $books as $book ) : ?>
<li>
<a href="<?php echo esc_url( get_permalink( $book ) ); ?>"><?php echo esc_html( get_the_title( $book ) ); ?></a>
<?php if ( ! empty( $attributes['showIsbn'] ) ) : ?>
<small><?php echo esc_html( get_post_meta( $book->ID, '_gdc_books_isbn', true ) ); ?></small>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php else : ?>
<p><?php esc_html_e( 'No books yet.', 'gdc-books' ); ?></p>
<?php endif; ?>
</div>get_block_wrapper_attributes() outputs the classes and inline styles for every design tool you enabled in supports — colour, alignment, padding — so the block respects the user's choices automatically.
| Static block | Dynamic block |
|---|---|
| Saves its final HTML into the post | Saves only attributes; PHP renders on each view |
| Fastest; no PHP at view time | Always up to date with current data |
| Good for: callouts, buttons, layouts | Good for: latest posts, book lists, anything from the database |
color support, set a background, and confirm it reaches the front end through get_block_wrapper_attributes().In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.