Shortcodes in WordPress

A shortcode in WordPress is a construction in the text that will be processed and replaced with the specified code/text. The shortcode is processed by a special PHP function specified when registering the shortcode.

The Shortcode API is a set of simple functions that allow theme and plugin developers to create their own shortcodes, which can then be used in content to display ready-made content blocks. For example, this could be a contact form.

For example, the following shortcode (in a post content) will add a photo gallery:


WordPress processes only registered shortcodes.

It is not possible to process all constructions of the form [___] because they may be used in the text but not be a shortcode.

Default WordPress Shortcodes

The following shortcodes are registered by default in WordPress:

Shortcode Syntax

WordPress shortcodes use square brackets [] because they are not part of any language.

The syntax looks like this:


[name /]

[name attribute="value" /]

[name attr1='value1' attr2="value2" ]

[name]Shortcode content.[/name]

[name attr="value"]Shortcode content.[/name]

There are two types of calls:

  • "self-closing" self-closing shortcode:

    • [name] - without parameters.
    • [name id="123" size="medium"] - with parameters.

    The shortcode can be closed with a slash at the end (this slash is called a self-closing marker).

    [example /]

    A space before the marker is optional. Spaces after the marker are not allowed. This marker is purely cosmetic and does not affect anything.

  • "enclosing" - content shortcode:

    • [name]text[/name] - without parameters.
    • [name size="medium"]text[/name] - with parameters.

    Content shortcodes cannot use a self-closing marker.

Shortcode Name

The shortcode name cannot contain the following characters: [ ] < > & / ' " - \x00-\x20 \s \n \t:

  • Square brackets: []
  • Angle brackets: < >
  • Ampersand: &
  • Slash: /
  • Invisible characters: space, newline, tab
  • Non-printable characters: \x00 - \x20
  • Quotes: ' "

The hyphen - is allowed in the shortcode name and does not cause any conflicts! However, the official documentation states that there may be conflicts - this is outdated information.

Shortcode Attributes

Attributes are not mandatory, at least one space is required between the name and attributes. If multiple attributes are specified, each attribute must be separated by at least one space.

Each attribute must match one of these formats:

[name attribute = 'value']

[name attribute = "value"]

[name attribute = value]

[name "value" "value2"]

[name "value"]

[name value]

A space is allowed before and after the = sign: attr = value, attr= value, attr =value.

Attribute Name

It can only contain the following characters: 0-9 A-Z a-z _ - (spaces in names are prohibited):

  • Uppercase and lowercase letters: A-Za-z.
  • Numbers: 0-9.
  • Underscore: _.
  • Hyphen: -.

The attribute name can be in any case. After parsing, the name will always be in lowercase: aTtR=value is the same as attr=value.

Attribute Value

The attribute value cannot contain the following characters: [ ] " '.

Notes on attribute values:

  • Attribute Quotes. Double quotes " are allowed inside values in single quotes ' and vice versa: [name foo='1 "2" 3' bar="4 '5' 6"]

  • Attribute Quotes. The shortcode can use double or single quotes to enclose attribute values, or not use them at all if there are no spaces in the value. [name foo='123' bar=456] is the same as [name foo="123" bar="456"].

  • Unnamed Attribute. The attribute name can be omitted. In this case, the attribute name will be the index of the $atts array. For example, [name 123] will create these parameters: $atts = array( 0 => 123 ). Such "positional" attributes can be used together with regular ones, and quotes can be used when the value contains spaces or other unusual characters.

  • Square Brackets. This is not allowed: [name foo="[value]"].

  • Escape Special Characters in attributes can be done through HTML encoding.

  • HTML entities < and > are supported in attributes to a limited extent. For example, this shortcode will not work correctly because of the > symbol:

    [name value1="35" value2="25" compare=">"]

    Version 4.0 can check HTML, so the following code will work:

    [name description="<b>Greetings</b>"]

    To fully support HTML in attributes, user input values should be encoded first and then decoded during processing.

  • Removable Characters. The following characters, if not escaped, will be:

    • Non-breaking space: \xC2\xA0 - will be replaced with a space.
    • Zero-width space: \xE2\x80\x8B - will be removed.


Escaping is needed when a shortcode is registered, but it should not be processed, but shown as is. An unregistered shortcode will not be processed anyway.

To escape, the shortcode should be enclosed in another pair of square brackets:

[[name attributes]]

[[name attributes]Any HTML or Shortcodes.[/name]]

Another option is to replace the characters [] with their HTML entities &#91; &#93;. For example, [name] can be written as &#91;name&#93;.

Enclosed (nested) Shortcodes

If there is a nested shortcode in a Content shortcode, it will not be processed. This is because parsing of shortcodes occurs only once for the content.

Example of nested shortcodes:

   [tag_b size="24"]

Another example of nested shortcodes:

[name]Foo: [my_name][/name]

Will be processed as:

<span class="caption">Foo: [my_name]</span>

If it is necessary to process a shortcode inside a shortcode, then when processing the content of the first shortcode, you need to use the do_shortcode() function to process the nested shortcodes.

function my_shortcode( $atts, $content ) {
	return '<span class="caption">' . do_shortcode( $content ) . '</span>';

Same name for nested shortcodes

The parser will not be able to process nested shortcodes with the same name because the nesting level is not counted. This limitation greatly speeds up the processing of shortcodes:


How It Works

When adding a shortcode via add_shortcode(), the shortcode is added to the global variable $shortcode_tags (the name and the handling function).

The do_shortcode() function is triggered on the the_content hook, which searches for and processes all shortcodes in the content.

All found shortcodes are passed one by one to the do_shortcode_tag() function. This function processes the data found by the regular expression and calls the function-handler of the added shortcode from $shortcode_tags (with parameters $attr, $content, $tag).

What the handler function returns will replace the shortcode in the content.

Before the do_shortcode() function is triggered (before parsing shortcodes), the content is processed by three key functions:

add_filter( 'the_content', 'do_blocks', 9 );
add_filter( 'the_content', 'wptexturize' );
add_filter( 'the_content', 'wpautop' );

add_filter( 'the_content', 'do_shortcode', 11 ); // AFTER wpautop().

do_shortcode() parses the content with one regular expression get_shortcode_regex(), so adding another shortcode does not slow down the processing speed - the regular expression will still work.

Creating a Shortcode

To create a shortcode, use the add_shortcode() function. It takes two parameters: the shortcode name and the name of the PHP function that will handle the shortcode.

Here is the simplest PHP code that registers a new shortcode [foobar]:

add_shortcode( 'foobar', 'foobar_shortcode' );

function foobar_shortcode( $atts ){
	return 'Hello! I am a shortcode.';

Now [foobar] in the post content will be replaced with the string: "Hello! I am a shortcode."

Shortcode Attributes

Let's add support for attributes to the shortcode from the example above. To do this, use the shortcode_atts() function:

add_shortcode( 'foobar', 'foobar_shortcode' );
function foobar_shortcode( $atts ) {

	$atts = shortcode_atts( [
		'name' => 'Noname',
		'age'  => 18,
	], $atts );

	return "My name is {$atts['name']} I am {$atts['age']} years old";

Now [foobar name="Victor" age="25"] in the content will be replaced with the string: "My name is Victor I am 25 years old."

shortcode_atts() defines which attributes the shortcode can have, sets their default values, and removes unrecognized attributes.

shortcode_atts( $defaults, $atts );

An associative array that describes the possible attributes of the shortcode (these are the array keys) and their default values.

IMPORTANT! The keys of the $defaults array must be in lower-case. Do not use camelCase or UPPER-CASE, because in $atts the keys are always in lower-case.


An uncleaned array of data specified in the shortcode. They will be compared with the array above.

$atts will contain an array of shortcode arguments specified by the user.

If a value is specified without a parameter name, it will be added to the indexed elements of the array. For example, for the shortcode [name value attr="val2" val3] we get:

Array (
	[0]    => value
	[attr] => val2
	[1]    => val3

Attribute names (keys of the $atts array) are always converted to lower-case, values are not processed in any way. For example, if we specified the shortcode as [myshortcode FOO="BAR"], then in $atts we get [ 'foo' => 'BAR' ].

Let's consider one more example.

Suppose we specified the shortcode [myname foo="456" bar="something"] in the content. Then:

add_shortcode( 'myname', 'my_shortcode_handler' );

function my_shortcode_handler( $atts ) {

	$atts = [
		'foo' => 456,
		'bar' => 'something'

	$data = shortcode_atts( [
		'title' => 'My Title',
		'foo'   => 123,
	], $atts );

	$data = [
		'title' => 'My Title',
		'foo'   => 456

From the code, it can be seen that we received one set of data, but after processing, we got another. What changed?

  • The received value $atts['foo'] overwrote the default value.
  • The received element $atts['bar'] was removed because it is not in the list of possible arguments.
  • The missing element $atts['title'] was added with the default value.

Output of the handler function

The return value of the shortcode handler function is inserted into the post content instead of the shortcode itself.

The result of the shortcode handler function should always be returned, not echoed to the screen. Use return instead of echo in the handler function.

Output Buffering

If the shortcode contains a lot of HTML code, you can use the ob_start() function to buffer the output and then return it:

function my_shortcode(){

	?> <HTML> <here> ... <?php

	return ob_get_clean();

Content Shortcodes

The above examples showed how to create "Single" (self-closing) shortcodes: [name]. But in addition to them, there are also "Content" (enclosing) shortcodes: [name]Content[/name].

For an Enclosing shortcode, the handler function will receive a second parameter containing the content.

function my_shortcode( $atts, $content )

A registered shortcode can always be used as either Single or Content, so when registering the shortcode, it is desirable to always specify a default value for the second parameter $content.

Or you can use the check empty( $content ) to differentiate the processing of Content and Single shortcodes. That is, one shortcode can be used as two different ones, depending on how it was used.

Let's consider an example:

add_shortcode( 'name', 'my_shortcode' );

function my_shortcode( $atts, $content ) {
	return '<span class="caption">' . $content . '</span>';

Now if you write the shortcode like this:

[name]My Caption[/name]

You will get this result:

<span class="caption">My Caption</span>

$content is passed to the function without any cleaning, so it can contain HTML:

[name]<a href="<a class="external" href="" rel="nofollow"></a>">My Caption</a>[/name]

Resulting in:

<span class="caption">
	<a href="">My Caption</a>

Sometimes, on the contrary, when HTML is not allowed, you need to clean the output by removing all HTML tags using the wp_strip_all_tags() function.

Attributes in Content Shortcodes

In a Content shortcode, you can also specify attributes just like in a Single shortcode. For example, let's allow the class attribute:

function my_shortcode( $atts, $content ) {

	$data = shortcode_atts( [
		'class' => 'caption',
	], $atts );

	return '<span class="' . esc_attr( $a['class'] ) . '">' . $content . '</span>';

[name class="headline"]My Caption[/name]

<span class="headline">My Caption</span>

Removing Shortcodes

To remove (deregister) a shortcode, you can use the function remove_shortcode( $name ).

To remove shortcodes from content, there is a function strip_shortcodes( $content )

You should remove the shortcode after it has been added, so it's best to call this function during the init event.

For example, let's say a plugin registers the shortcode [awesome], but we want to remove it. We can do it like this:

add_action( 'init', 'unregister_shortcodes', 20 );
function unregister_shortcodes(){
	remove_shortcode( 'awesome' );

Also, in the Shortcode API, there is a function that removes all registered shortcodes at once: see remove_all_shortcodes().

PHP Functions

add_shortcode() Adds a new shortcode.
shortcode_atts() Combine user attributes with known attributes and fill in defaults when needed.
remove_shortcode() Removes hook for shortcode.
remove_all_shortcodes() Clear all shortcodes.
do_shortcode() Search content for shortcodes and applies the registered functions to the found shortcodes.
apply_shortcodes() Search content for shortcodes and filter shortcodes through their hooks.
has_shortcode() Whether the passed content contains the specified shortcode
shortcode_exists() Whether a registered shortcode exists named $tag
strip_shortcodes() Remove all shortcode tags from the given content.

Full list of functions.



Shortcodes are processed after the post content is processed by the wpautop() function. Therefore, the returned string from the handler function will not be processed by this function.

wpautop() recognizes registered shortcodes and does not process them (does not add p or br tags around or inside).


Shortcodes are processed after the post content is processed by the wptexturize() function. It processes unregistered shortcodes as regular text and replaces quotes in them.

To prevent this from happening, you can wrap the shortcode in the <code> or <pre> tag. Text inside these tags is not processed by wptexturize(). For example:

[name attr=“value”]

<code>[name attr="value"]</code>

Or you can add the shortcode to the list of non-texturized shortcodes. This is done via the no_texturize_shortcodes hook.

Different functions for one shortcode

If you register two shortcodes with the same names but different handler functions, only the last function will be applied. That is, the shortcode added last will be triggered.

add_shortcode( 'name', 'name_shortcode' );
add_shortcode( 'name', 'noname_shortcode' );

function name_shortcode(){
	return 'name shortcode';

function noname_shortcode(){
	return 'noname shortcode';

[name] in the content will output the string "noname shortcode"

One function for different shortcodes

add_shortcode( 'foo', 'my_function' );
add_shortcode( 'bar', 'my_function' );

If two shortcodes use the same handler function, you can get the name of the current shortcode in the code - it is passed to the handler function as the third parameter:

function my_shortcode( $attr, $content, $tag ){

	if( 'foo' === $tag ){
		// Processing the [foo] shortcode

	elseif( 'bar' === $tag ){
		// Processing the [bar] shortcode


Mixed usage of one shortcode

The parser cannot handle the mixed usage of a single shortcode, when it is used as both Self-closed and Enclosing. For example:

[name attr='non-enclosing' /] some text [name]shortcode text[/name]

Instead of parsing this string as two separate shortcodes separated by the text some text, the parser will interpret it as a single Content shortcode with the text some text [name]shortcode text.

Unregistered shortcodes

Some plugin authors do not register shortcodes, for example, to disable a nested shortcode until the handler function of the parent shortcode is called. This can cause the shortcodes to become non-functional. For example:

[tag_a unit="north"]
   [tag_b size="24"]
	  [tag_c color="red"]

If shortcodes tag_b and tag_c are not registered, wptexturize() will turn the code into the following before the tag_a shortcode is processed:

[tag_a unit="north"]
   [tag_b size=&#8221;24&#8221;]
	  [tag_c color=&#8221;red&#8221;]

Unregistered shortcodes will be considered as regular text, which has no special meaning. To make the shortcode meaningful for wptexturize(), you can add it to the list via the no_texturize_shortcodes hook:

add_shortcode( 'tag_a', 'my_tag_a_handler' );
add_filter( 'no_texturize_shortcodes', 'ignore_tag_a' );

function ignore_tag_a( $list ) {
	$list[] = 'tag_a';
	return $list;

Unclosed shortcodes

In some cases, the shortcode parser cannot correctly handle a single shortcode that is used as both a Single and Content shortcode. For example, in this case, the syntax parser will correctly identify only the second instance of the shortcode:


However, it will correctly parse both in this case:


Shortcode in an HTML tag attribute

Starting from version 4.2.3, restrictions have been imposed on the use of shortcodes within HTML tags. For example, the following shortcode will not work correctly, as it is nested inside a script attribute:

<a onclick="[tag]">

This can be bypassed by creating a shortcode that outputs the entire necessary HTML, instead of just a single value.

[link onclick="tag"]

Shortcode API Tests

Tests with interesting examples of errors and unusual syntax can be found at this link: