EthicsComputerCourses
Join training +91 90532 10052

Shortcodes vs Blocks

WordPress · Lesson 25 of 30 Advanced 7 min read Updated

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.

ShortcodeBlock
Looks like in the editorText in square brackets: [bf_seats batch="Class 10"]A visual block with a preview and settings panel
IntroducedWordPress 2.5 (2008)WordPress 5.0 (2018)
SettingsTyped by hand as attributesControls in the sidebar
Written inPHPJavaScript and PHP, or PHP only (7.0+)
Works in Site Editor templatesThrough the Shortcode blockYes, 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:

PHP
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:

Text
[bf_seats batch="Class 12 Science" seats="3"]

Step 3: Preview the page. Where the shortcode was, you see:

Output
<p class="bf-seats">Class 12 Science: only 3 seats left</p>

How it works:

  • add_shortcode() links the tag bf_seats to your function.
  • shortcode_atts() fills in defaults for anything the writer left out, and drops unknown attributes.
  • The function returns HTML. Never echo inside 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:

Terminal
cd wp-content/plugins
npx @wordpress/create-block@latest bf-seats --variant dynamic
cd bf-seats
npm start

This 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:

JSON
{
  "$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
<?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

PHP
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?

SituationChoose
Maintaining an old site full of shortcodesKeep the shortcodes working; add blocks for new features
A small server-rendered widget with a few settingsPHP-only block (WordPress 7.0+)
A rich, interactive block with live editingStandard block with block.json and JavaScript
Output inside a classic theme template or widget areaA 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-block scaffolds a complete block plugin.
  • WordPress 7.0 can register simple PHP-only blocks with supports.autoRegister and a render_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
PHP
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 ) );
} );
Output
<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?

2. What does shortcode_atts() do?

3. Which file describes a modern block's name, attributes and scripts?

4. What does a PHP-only block in WordPress 7.0 need in its settings?

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