What a shortcode is
A shortcode is a short tag in square brackets that you type into a post or page. When the page is shown, WordPress replaces it with whatever your PHP function returns:
Welcome to [sitename]becomes "Welcome to My WordPress Site". Plugins use shortcodes for forms, galleries and buttons. Today new features should usually be blocks, but you will meet shortcodes constantly, they work in every theme and in classic content, and they are quick to write.
A first shortcode
<?php
/**
* Plugin Name: Bakery Shortcodes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_shortcode( 'sitename', function () {
return esc_html( get_bloginfo( 'name' ) );
} );wp plugin activate bakery-shortcodesPlugin 'bakery-shortcodes' activated.
Success: Activated 1 of 1 plugins.<?php
echo do_shortcode( 'Welcome to [sitename]' ), "\n";Welcome to My WordPress Sitedo_shortcode() runs shortcodes in a string. WordPress does this for you on post content (through the the_content filter); you call it yourself in templates or for text from other places.
Attributes
Shortcodes can take attributes, such as [price amount="4.5" currency="GBP"]. The callback receives them as an array. Use shortcode_atts() to fill in defaults and ignore unknown ones, then escape what you print:
<?php
/**
* Plugin Name: Bakery Shortcodes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_shortcode( 'sitename', fn() => esc_html( get_bloginfo( 'name' ) ) );
add_shortcode( 'price', function ( $atts ) {
$atts = shortcode_atts(
[
'amount' => '0',
'currency' => 'GBP',
],
$atts,
'price'
);
return sprintf(
'<span class="price">%s %s</span>',
esc_html( number_format( (float) $atts['amount'], 2 ) ),
esc_html( strtoupper( $atts['currency'] ) )
);
} );<?php
echo do_shortcode( '[price amount="4.5"]' ), "\n";
echo do_shortcode( '[price amount="12" currency="usd"]' ), "\n";
echo do_shortcode( '[price]' ), "\n";
echo do_shortcode( '[price amount="9" colour="red"]' ), "\n";<span class="price">4.50 GBP</span>
<span class="price">12.00 USD</span>
<span class="price">0.00 GBP</span>
<span class="price">9.00 GBP</span>The last line shows shortcode_atts() at work: the unknown colour attribute is ignored. The third argument ('price') is the shortcode name; it lets other code filter the attributes.
User-written attribute values are untrusted input. Cast numbers with (float) or absint() and escape text with esc_html() or esc_attr(), as above.
Enclosing shortcodes
A shortcode can wrap content: [note]Text[/note]. The callback gets the content as its second argument:
<?php
add_shortcode( 'note', function ( $atts, $content = null ) {
$atts = shortcode_atts( [ 'type' => 'info' ], $atts, 'note' );
return sprintf(
'<div class="note note-%s">%s</div>',
esc_attr( sanitize_html_class( $atts['type'] ) ),
do_shortcode( wp_kses_post( (string) $content ) ) // allow shortcodes inside, and only safe HTML
);
} );<?php
/**
* Plugin Name: Bakery Shortcodes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/notes.php';
add_shortcode( 'sitename', fn() => esc_html( get_bloginfo( 'name' ) ) );<?php
echo do_shortcode( '[note type="warning"]Contains <strong>nuts</strong>. Made at [sitename].[/note]' ), "\n";<div class="note note-warning">Contains <strong>nuts</strong>. Made at My WordPress Site.</div>Returning HTML that is built in pieces
If a shortcode needs a bigger template, do not echo. Either build a string, or capture an included file with output buffering and return it:
add_shortcode( 'menu_card', function () {
ob_start();
include plugin_dir_path( __FILE__ ) . 'templates/menu-card.php';
return ob_get_clean();
} );ob_start() captures anything printed; ob_get_clean() returns it and stops capturing.
Shortcodes in templates and widgets
Post content processes shortcodes automatically. In a template, call echo do_shortcode( '[sitename]' );. In the block editor, the Shortcode block holds a shortcode, and it also works in a Paragraph block.
Testing a shortcode
Write the shortcode into a test page and open it, or call do_shortcode() from WP-CLI as this lesson does. Try the awkward cases too: no attributes, an unknown attribute, quotes or angle brackets inside an attribute, and an enclosing shortcode with nothing between its tags. Each of these should produce valid, escaped HTML and never a PHP warning.
Shortcode or block?
Use a shortcode for a quick feature that must work in classic content, widgets or other themes. Use a block (see Creating a custom block) when editors should see and style the thing visually in the editor. Many plugins offer both.
Common mistakes
- Echoing instead of returning, which prints the output at the top of the page.
- Not escaping attribute values, which allows script injection by anyone who can write posts.
- Unprefixed shortcode names like
[button]that clash with another plugin. - Using shortcodes for everything, which leaves orphaned
[tags]in posts if the plugin is later removed.
Exercise
Write a [greeting name="Sam"] shortcode that returns "Hello, Sam!" and falls back to "Hello, friend!" when no name is given.
Show one possible answer
add_shortcode( 'greeting', function ( $atts ) {
$atts = shortcode_atts( [ 'name' => 'friend' ], $atts, 'greeting' );
return 'Hello, ' . esc_html( $atts['name'] ) . '!';
} );shortcode_atts() supplies the default; esc_html() makes the name safe to print.
Key points
- A shortcode is a tag in square brackets, such as [sitename], that WordPress replaces with the output of a PHP function.
- add_shortcode( 'name', $callback ) registers it; the callback receives the attributes and any enclosed content and must return a string, not echo.
- shortcode_atts() sets defaults for attributes, and attribute values must be escaped on output.
- Shortcodes are the older way to add dynamic content; blocks are the modern way, but shortcodes still work everywhere.