Shortcodes are small tags in square brackets — like [gallery] or [contact-form id="3"] — that WordPress replaces with dynamic content when a page is displayed. They’re older than the block editor but still widely used by plugins and still the quickest way to drop dynamic output into content.

Using shortcodes

In the block editor, add a Shortcode block and paste the tag. Shortcodes also work in classic editor content and in Text widgets.

Built-in shortcodes include:

Shortcode Output
[gallery ids="12,13,14"] Image gallery
[caption] Image with caption
[audio src="…"] / [video src="…"] Media players
[embed]https://…[/embed] oEmbed content
[playlist ids="…"] Audio/video playlist

Plugins add their own: forms, pricing tables, sliders, recipe cards and so on.

Creating a shortcode

Put custom shortcodes in a small plugin (or a child theme’s functions.php) so they survive theme changes:

<?php
/**
 * Plugin Name: Site Shortcodes
 */
add_shortcode( 'year', fn() => date_i18n( 'Y' ) );

Now © [year] Example Ltd always shows the current year.

Rule: a shortcode callback must return its output, never echo it. Echoing prints the content in the wrong place (usually at the top of the post).

Attributes

add_shortcode( 'button', function ( $atts ) {
    $atts = shortcode_atts( [
        'url'   => '#',
        'label' => 'Learn more',
        'style' => 'primary',
    ], $atts, 'button' );

    $style = in_array( $atts['style'], [ 'primary', 'secondary' ], true ) ? $atts['style'] : 'primary';

    return sprintf(
        '<a class="btn btn-%s" href="%s">%s</a>',
        esc_attr( $style ),
        esc_url( $atts['url'] ),
        esc_html( $atts['label'] )
    );
} );

Usage: [button url="/pricing/" label="See pricing"].

shortcode_atts() fills defaults and drops unknown attributes. Escape every attribute on output — authors (or attackers with author accounts) control them.

Enclosing shortcodes

add_shortcode( 'note', function ( $atts, $content = '' ) {
    return '<div class="note">' . wp_kses_post( do_shortcode( $content ) ) . '</div>';
} );

Usage: [note]Back up before upgrading. [button url="/backup/"][/note]. Calling do_shortcode() on $content allows nested shortcodes.

Loading assets only when needed

Don’t enqueue a shortcode’s CSS/JS on every page. Register it globally, enqueue it inside the callback:

add_action( 'wp_enqueue_scripts', fn() => wp_register_style( 'pricing', plugins_url( 'pricing.css', __FILE__ ) ) );

add_shortcode( 'pricing', function () {
    wp_enqueue_style( 'pricing' );
    return '<div class="pricing">…</div>';
} );

Shortcodes vs blocks

Shortcode Block
Editing experience Text tag, no preview Visual, with settings panel
Development effort A few lines of PHP JavaScript + build step (or block.json with PHP render)
Portability Works in classic content, widgets, many builders Block editor only

For content editors, blocks are friendlier. For quick dynamic snippets, shortcodes are still perfectly fine. A middle ground is a dynamic block with a PHP render_callback that reuses the same function as your shortcode.

Troubleshooting

  • Shortcode shows as plain text — the plugin that provides it is inactive, or the content area doesn’t process shortcodes (call do_shortcode() in custom templates).
  • Output appears at the top of the post — the callback echoes instead of returning. Wrap output with ob_start() / ob_get_clean() if you must use templates that echo.
  • Leftover tags after removing a plugin — search the database with wp db search '[oldshortcode' and remove them, or register an empty handler: add_shortcode( 'oldshortcode', '__return_empty_string' );.

More WordPress development guides: Customizer options and custom post meta.

  • WordPress
  • Development
  • PHP