A shortcode is a small tag in post content, written in square brackets, that WordPress replaces with the output of a PHP function.
[greeting name="Isha"]
Register one
<?php
add_shortcode("greeting", function (array|string $atts = []): string {
$atts = shortcode_atts(["name" => "friend"], (array) $atts, "greeting");
return "Hello, " . esc_html($atts["name"]) . "!";
});
echo do_shortcode('[greeting name="Isha"]') . "\n";
echo do_shortcode("[greeting]");
Hello, Isha!
Hello, friend!
add_shortcode($tag, $callback)registers the tag.- The callback receives the attributes as an array (it can be an empty string when there are none, so cast to array).
shortcode_atts()merges the given attributes with defaults, and drops unknown ones.- The callback returns a string. WordPress puts it in the content.
Return, never echo
A shortcode must return its output. If it echoes, the text appears at the top of the page, before the content.
If your code prints a lot of HTML, capture it:
<?php
add_shortcode("card", function (): string {
ob_start();
?>
<div class="card">Captured with output buffering</div>
<?php
return trim(ob_get_clean());
});
echo do_shortcode("[card]");
<div class="card">Captured with output buffering</div>
Enclosing shortcodes
A shortcode can wrap content.
[note type="warning"]Be careful here.[/note]
<?php
add_shortcode("note", function (array|string $atts = [], ?string $content = null): string {
$atts = shortcode_atts(["type" => "info"], (array) $atts, "note");
$type = in_array($atts["type"], ["info", "warning"], true) ? $atts["type"] : "info";
return '<div class="note note-' . esc_attr($type) . '">' . do_shortcode((string) $content) . "</div>";
});
echo do_shortcode('[note type="warning"]Be careful here.[/note]');
<div class="note note-warning">Be careful here.</div>
The second argument is the content between the tags. Calling do_shortcode() on it allows shortcodes inside shortcodes.
Validate attributes
Attributes come from whoever edits the post. Treat them as input: allow-list values, cast numbers, escape on output.
<?php
add_shortcode("stars", function (array|string $atts = []): string {
$atts = shortcode_atts(["count" => 5], (array) $atts, "stars");
$count = max(0, min(10, (int) $atts["count"]));
return str_repeat("*", $count);
});
echo do_shortcode('[stars count="3"]') . " " . do_shortcode('[stars count="999"]');
*** **********
Use it in a template
<?php echo do_shortcode('[greeting name="Isha"]'); ?>
A shortcode that queries posts
<?php
add_shortcode("latest_posts", function (array|string $atts = []): string {
$atts = shortcode_atts(["count" => 3], (array) $atts, "latest_posts");
$posts = get_posts(["numberposts" => max(1, min(10, (int) $atts["count"]))]);
if (!$posts) {
return "";
}
$items = array_map(
fn(WP_Post $p) => '<li><a href="' . esc_url(get_permalink($p)) . '">' . esc_html(get_the_title($p)) . "</a></li>",
$posts
);
return "<ul>" . implode("", $items) . "</ul>";
});
Shortcodes and blocks
The block editor (Gutenberg) uses blocks for the same purpose. Blocks have a visual editor. Shortcodes are still common for older themes and plugins, and the Shortcode block lets you use them inside the block editor.
Performance
A shortcode runs every time the content is rendered. If it does heavy work (queries, API calls), cache the result with a transient.
Remove a shortcode
remove_shortcode("greeting") unregisters it, and shortcode_exists("greeting") checks. strip_shortcodes($content) removes shortcode tags from text.