Shortcodes vs Blocks
By the end of this lesson you will write a shortcode, build a block that does the same job, and know when to use each. You will also try the PHP-only way of registering a block that arrived in WordPress 7.0, which lets PHP developers create simple blocks without a JavaScript build step.
Two ways to put dynamic content in a page
Sometimes a page needs content that code produces: "Only 5 seats left in the Class 10 batch", the next exam date, the latest three courses. WordPress offers two tools.
| Shortcode | Block | |
|---|---|---|
| Looks like in the editor | Text in square brackets: [bf_seats batch="Class 10"] | A visual block with a preview and settings panel |
| Introduced | WordPress 2.5 (2008) | WordPress 5.0 (2018) |
| Settings | Typed by hand as attributes | Controls in the sidebar |
| Written in | PHP | JavaScript and PHP, or PHP only (7.0+) |
| Works in Site Editor templates | Through the Shortcode block | Yes, natively |
A shortcode is like a code word you write on a form, and the office clerk replaces it later. A block is like a pre-printed box on the form with labelled fields: easier to fill correctly.
Writing a shortcode, step by step
Step 1: In your plugin (for example the bright-courses plugin), add:
add_shortcode( 'bf_seats', function ( $atts ) {
$atts = shortcode_atts(
[
'batch' => 'Class 10',
'seats' => 5,
],
$atts,
'bf_seats'
);
return sprintf(
'<p class="bf-seats">%s: only %d seats left</p>',
esc_html( $atts['batch'] ),
absint( $atts['seats'] )
);
} );Step 2: Edit a page and add a Shortcode block. Type:
[bf_seats batch="Class 12 Science" seats="3"]Step 3: Preview the page. Where the shortcode was, you see:
<p class="bf-seats">Class 12 Science: only 3 seats left</p>
How it works:
add_shortcode()links the tagbf_seatsto your function.shortcode_atts()fills in defaults for anything the writer left out, and drops unknown attributes.- The function returns HTML. Never
echoinside a shortcode: echoed text appears at the top of the page instead of where the shortcode sits. - Everything typed by a writer is escaped (
esc_html(),absint()), because shortcode attributes are user input.
The problems with shortcodes
Shortcodes still work and you will see them on many sites. But they have weaknesses:
- No preview: writers see square brackets, not the result.
- Easy to break: a missing quote or bracket fails silently.
- Lock-in: if the plugin is removed, the raw
[bf_seats ...]text shows on the live site. - Memory: writers must remember attribute names.
Blocks solve these problems, so new features should usually be blocks.
Building a block: the standard way
A modern block is described by a block.json file. The quickest start is the official scaffolding tool, which needs Node.js installed:
cd wp-content/plugins
npx @wordpress/create-block@latest bf-seats --variant dynamic
cd bf-seats
npm startThis creates a ready plugin folder with block.json, editor JavaScript in src/, a build step and a PHP file that registers the block. npm start rebuilds the files whenever you save. For a dynamic block, block.json looks roughly like this:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "brightfuture/seats",
"title": "Seats Left",
"category": "widgets",
"icon": "megaphone",
"attributes": {
"batch": { "type": "string", "default": "Class 10" },
"seats": { "type": "integer", "default": 5 }
},
"editorScript": "file:./index.js",
"render": "file:./render.php"
}And render.php prints the front-end HTML. WordPress gives it the $attributes array:
<?php
printf(
'<p %s>%s: only %d seats left</p>',
get_block_wrapper_attributes(),
esc_html( $attributes['batch'] ),
absint( $attributes['seats'] )
);get_block_wrapper_attributes() adds the block's standard classes and any styles chosen in the editor, such as colours.
Static blocks save their HTML into the post when you click Save. Dynamic blocks, like this one, save only their settings and build the HTML on every page view, which suits changing data like seat counts or latest posts.
New in WordPress 7.0: PHP-only blocks
add_action( 'init', function () {
register_block_type( 'brightfuture/seats-php', [
'title' => __( 'Seats Left (PHP)', 'bright-courses' ),
'attributes' => [
'batch' => [ 'type' => 'string', 'default' => 'Class 10', 'label' => __( 'Batch', 'bright-courses' ) ],
'seats' => [ 'type' => 'integer', 'default' => 5, 'label' => __( 'Seats left', 'bright-courses' ) ],
],
'supports' => [ 'autoRegister' => true ],
'render_callback' => function ( $attributes ) {
return sprintf(
'<p %s>%s: only %d seats left</p>',
get_block_wrapper_attributes(),
esc_html( $attributes['batch'] ),
absint( $attributes['seats'] )
);
},
] );
} );Open the editor and search the inserter for "Seats Left (PHP)". Insert it, and the sidebar shows a text field for Batch and a number field for Seats left. The preview comes from your PHP.
This approach has limits today: automatic controls cover string, number, integer and boolean attributes; there are no inner blocks and no media or rich-text fields; each change in the editor asks the server to render again. For richer blocks, use the standard block.json and JavaScript approach.
Which should you choose?
| Situation | Choose |
|---|---|
| Maintaining an old site full of shortcodes | Keep the shortcodes working; add blocks for new features |
| A small server-rendered widget with a few settings | PHP-only block (WordPress 7.0+) |
| A rich, interactive block with live editing | Standard block with block.json and JavaScript |
| Output inside a classic theme template or widget area | A shortcode or a PHP function is still fine |
Many plugins offer both: a block for the editor and a shortcode for older places. You can do that too, with both calling the same PHP function.
Common mistakes
- Echoing in a shortcode. The output jumps to the top of the content. Always return.
- Not escaping shortcode attributes. They are typed by users; treat them as untrusted.
- Using a shortcode when a pattern would do. If content does not need code (a styled box with fixed text), a pattern is simpler.
- Changing a block's name later. Saved content refers to the name (
brightfuture/seats); renaming breaks existing blocks.
Key points
- Shortcodes are bracketed tags handled by
add_shortcode(); blocks are visual, with previews and settings. shortcode_atts()sets defaults; shortcode callbacks must return, not echo, and must escape attributes.- Standard blocks are described in
block.json; dynamic blocks render with PHP on every view. @wordpress/create-blockscaffolds a complete block plugin.- WordPress 7.0 can register simple PHP-only blocks with
supports.autoRegisterand arender_callback. - Prefer blocks for new features; keep shortcodes for older content and templates.
Exercise
Write a shortcode [bf_next_test subject="Physics" date="2026-11-15"] that prints "Next Physics test: 15 November 2026". Use wp_date() (or PHP's date()) with the format j F Y to format the date, and fall back to "date to be announced" if the date is missing or invalid.
Show solution
add_shortcode( 'bf_next_test', function ( $atts ) {
$atts = shortcode_atts( [ 'subject' => 'Maths', 'date' => '' ], $atts, 'bf_next_test' );
$time = strtotime( $atts['date'] );
$when = $time ? wp_date( 'j F Y', $time ) : 'date to be announced';
return sprintf( '<p>Next %s test: %s</p>', esc_html( $atts['subject'] ), esc_html( $when ) );
} );<p>Next Physics test: 15 November 2026</p>
strtotime() returns false for an empty or invalid date, so the fallback text appears instead. wp_date() formats the date using the site's timezone and language.
Quick Quiz
1. Why must a shortcode callback return its HTML instead of echoing it?
WordPress replaces the shortcode with the returned string; echo prints too early.
2. What does shortcode_atts() do?
It fills in defaults so your code always has every attribute.
3. Which file describes a modern block's name, attributes and scripts?
block.json is the block's metadata file.
4. What does a PHP-only block in WordPress 7.0 need in its settings?
autoRegister lets WordPress register the block in the editor without JavaScript.
Learn WordPress with a mentor
Free lessons get you started. Our WordPress & Website Building training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
