Shortcode Generator
Generate an add_shortcode() handler with typed attributes, shortcode_atts defaults, escaped output and an optional enclosing form.
<?php
/**
* Handles the [my_plugin_button] shortcode.
*
* @param array $atts Shortcode attributes.
* @return string HTML, already escaped.
*/
function my_plugin_button_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'label' => 'Read more',
'url' => '#',
),
$atts,
'my_plugin_button'
);
// Each value is escaped for the context its kind implies.
$safe_label = esc_html( $atts['label'] );
$safe_url = esc_url( $atts['url'] );
// The visible text: the wrapped content when this shortcode encloses,
// otherwise the first attribute above. Swap it for whichever value you want.
$text = $safe_label;
$class_attr = ' class="' . esc_attr( 'my-plugin-button' ) . '"';
// href falls back to # when the shortcode has no url attribute.
$href = isset( $safe_url ) ? $safe_url : '#';
return '<a' . $class_attr . ' href="' . $href . '">' . $text . '</a>';
}
add_shortcode( 'my_plugin_button', 'my_plugin_button_shortcode' );
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
Name the shortcode, list the attributes it takes and pick what it outputs. The generator writes the handler with shortcode_atts() defaults and the escaping each attribute’s kind needs.
How to use
- Pick the tag an author will type. Prefix it: a bare
[button]collides with every other plugin that had the same idea. - Add the attributes. Each one gets a default, so a shortcode written without it still renders.
- Set each attribute’s kind. Text is escaped with
esc_html(), url withesc_url(), number is cast, and html goes throughwp_kses_post(). - Turn on enclosing if the shortcode wraps content, as in
[tag]…[/tag]. The handler then receives that content and runs nested shortcodes on it. - Paste the result into your plugin. It hooks itself with
add_shortcode()at the bottom.
Example
A callout that wraps content in a div, with a tone attribute and a column count:
$atts = shortcode_atts(
array(
'tone' => 'warning',
'columns' => '2',
),
$atts,
'acme_callout'
);
$safe_tone = esc_html( $atts['tone'] );
$safe_columns = (float) $atts['columns'];
$text = $content ? wp_kses_post( do_shortcode( $content ) ) : '';
Written in a post as [acme_callout tone="danger"]Back up first.[/acme_callout].
The escaping happens on the way out, at the point the value is printed, which is where WordPress expects it rather than on the way in.
Pitfalls
- An unprefixed tag is a collision waiting to happen. Shortcodes share one global namespace across every plugin and theme on the site.
- A shortcode handler must return its output, never echo it. Echoing prints the content at the top of the page, above everything else.
shortcode_atts()lowercases attribute names, so[tag Label="x"]arrives aslabel. Name attributes in lowercase and expect them that way.- Attribute values with spaces need quotes in the post:
[tag label="Read more"]. Without quotes only the first word arrives. - An enclosing shortcode gets
$contentonly when the closing tag is present.[tag]on its own passes null, which is why the generated code guards it. - Running
do_shortcode()on wrapped content is what makes nesting work, and it also means anything an author nests is executed. Keepwp_kses_post()around it. - Numbers arrive as strings. The generated cast is the difference between
2and"2 onclick=...". - Shortcodes do not run in widgets or excerpts by default, and block themes render them only inside a shortcode block or post content.
Compatibility
add_shortcode() and shortcode_atts() have been stable since WordPress 2.5, and wp_kses_post() since 2.9, so the generated code runs on any WordPress still receiving updates. It targets PHP 7.0 and later. The tool itself runs in your browser with no network request.
Frequently asked questions
Should I write a shortcode or a block?
Why does my shortcode output appear at the top of the page?
How do I use this in a template file?
echo do_shortcode( '[my_plugin_button label="Buy"]' );. Passing user input into that string is the one way to reintroduce the escaping problem the handler solves.Do attribute names have to be lowercase?
shortcode_atts() lowercases the keys it receives, so an uppercase name never matches your defaults array.Can a shortcode take no attributes at all?
shortcode_atts() simply has nothing to merge.