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.