Imagine if WordPress allowed content authors to include PHP snippets in their content. That would introduce a big security hole and eventually lead to disaster! Shortcodes solve that problem by allowing such code to run in a controlled fashion, thus allowing for dynamic content. In this article we will discuss how to create shortcodes.

What is a shortcode

A shortcode is essentially a square-bracketed tag and can potentially contain attributes.. For example, if we were to use the built-in gallery shortcode, we would write something like [gallery id="5"]. Note that the name must follow immediately after the opening square bracket, otherwise WordPress will not recognize it. However, there can be trailing spaces before the closing square bracket.

Shortcodes can also enclose content by utilizing a closing [/shortcode] tag like so: [hug]Don't let go[/hug]. In that case, the enclosed content will be passed to the shortcode’s handler in order to be processed. Having said that, it is obvious that [foo] is functionally equivalent to [foo][/foo].

So how do I create a shortcode?

Before we begin, we have to note that, although plugins and themes use the same API, shortcode development belongs to the plugin realm. The practical reason is that, if you change the active theme, your shortcodes will no longer work. Having said that, let’s dive into how to create shortcodes.

Let’s assume for our first simple example that we need a shortcode that inserts the current year. To do that we would use the add_shortcode() function by adding the following code to our plugin:

function current_year() {
  return date('Y');
}

function tutorial_init() {
  add_shortcode('year','current_year');
}

add_action('init','tutorial_init');
PHP

In the snippet above, current_year() is the shortcode handler and it is passed to add_shortcode() along with the shortcode’s name. Its return value is the content with which the shortcode will be replaced. This means that, every time we insert [year] in our content, WordPress will replace it with 2024. Notice that the handler does not accept any arguments, however when it is eventually called by WordPress, it is given three of them, as we will see below.

Passing attributes

Now let’s say we want to paint the background of the shortcode’s output by passing a CSS color attribute, like so: [year bg="cyan"]. To do so, our handler needs to read the attributes of the shortcode, which is its first argument:

function current_year($atts) {
  $style="";
  if(isset($atts['bg'])) $style=" style='background-color:".$atts['bg']."'";
  return "<span$style>".date('Y')."</span>";
}

function tutorial_init() {
  add_shortcode('year','current_year');
}

add_action('init','tutorial_init');
PHP

The logic here is very simple: wrap the current year in a <span> and, if there’s a bg attribute provided, add the corresponding style to it. So now [year bg="cyan"] will look like 2024. Obviously, $atts is a key/value array that contains the attributes.

Of course, this is a very simple scenario. In more complex situations, we would need to use the shortcode_atts() function to make things easier.

Enclosed content

So far we have explained how to create shortcodes that work as standalone tags. But what would happen if we enclosed some content inside our [year] shortcode? Well, the enclosed content would be completely ignored and replaced by 2024. That is because our handler is not aware of the enclosed text.

For our next example, we will develop a [logged_in] shortcode to hide pieces of content from non logged-in visitors. In order to do that, we will use the handler’s second parameter, which is the content surrounded by the shortcode:

function logged_in_content($atts,$content) {
  if(is_user_logged_in()) return do_shortcode($content);
  return "";
}

function tutorial_init() {
  add_shortcode('year','logged_in_content');
}

add_action('init','tutorial_init');
PHP

Notice that, if the current visitor is logged in, we don’t just return the enclosed content. We use the do_shortcode() function to further process $content for nested shortcodes. That is because WordPress only processes shortcodes one level deep, for performance reasons.

It is important to remember that WordPress has no way of distinguishing between enclosing and non-enclosing forms of a shortcode. Moreover, shortcode parsing is done in a “greedy” fashion. Consequently, in cases like [logged_in] foo [logged_in] bar [/logged_in], the shortcode handler will be passed foo [logged_in] bar as enclosed content.

The third parameter

There are times when attaching the same handler to multiple shortcodes makes sense. In those situations, the handler will eventually need to know which shortcode is being processed. That information, the shortcode’s name, is passed to the handler via its third parameter.

For example, consider the following piece of code:

function format_content($atts,$content,$name) {
	$formats=['bold'=>'b','italic'=>'i','quote'=>'q',];
	$tag=$formats[$name];
	return do_shortcode("<$tag>$content</$tag>");
}

function tutorial_init() {
	add_shortcode('bold','format_content');
	add_shortcode('italic','format_content');
	add_shortcode('quote','format_content');
}

add_action('init','tutorial_init');
PHP

In the example above, the same handler is used to process three different shortcodes. It utilizes the $name parameter to decide the appropriate HTML tag to use. So if we were to insert [quote]Something [italic]like [bold]this[/bold][/italic][/quote] in our content, we would get Something like this.