Skip to content
🛠 How to add your own shortcode in WordPress: add_shortcode() breakdown [+2 examples]

🛠 How to add your own shortcode in WordPress: add_shortcode() breakdown [+2 examples]

Created a snippet, added it to functions.php, and WordPress says "cheatin' uh?" Sound familiar? Shortcodes, WordPress's core extension mechanism, and add_shortcode() unlocks it in 15 lines of code. But between "Hello World" and a real-world scenario lie a few pitfalls: return vs echo, callback parameter order, and tag conflicts.

Shortcodes appeared back in WordPress 2.5 and have since become the foundation of the ecosystem: contact forms, galleries, sliders, custom field output, all of it lives inside square brackets. add_shortcode() hasn't changed in years, and you can master it in half an hour.

Below, a complete breakdown of the function with two ready-to-use examples: from an auto-updating year in the footer to a site user list.

💡 Quick overview:

  • add_shortcode() syntax and parameters, tag and callback
  • The rule: always return, never echo
  • The init hook, why your shortcode may not work without it
  • Two ways to pass data: content between tags and attributes
  • Ready-to-use code: a shortcode for auto-year and a user list

How add_shortcode() works in WordPress

The add_shortcode() function is the entry point for registering any custom shortcode. It takes two required parameters: a unique tag and the name of the callback function that will return the content.

According to the WordPress documentation, the callback receives three arguments in a fixed order:

  • $attributes, an array of attributes passed in the shortcode
  • $content, the text between the opening and closing tags
  • $tag, the tag name the shortcode is registered with

A basic example, a shortcode that outputs "Hello World!":

1// Shortcode callback function
2function hello_world_shortcode_callback( $attributes, $content, $tag ) {
3 return 'Hello World!';
4}
5// Shortcode registration
6function hello_world_shortcode() {
7 add_shortcode( 'hello-world', 'hello_world_shortcode_callback' );
8}
9add_action( 'init', 'hello_world_shortcode' );

The code goes into your child theme's functions.php or via a Code Snippets plugin. After that, [hello-world] in the post editor will output "Hello World!".

Why return, not echo

Note the return inside the callback function. If you use echo, the output happens before WordPress builds the rest of the page, "Hello World!" ends up at the very top, and the surrounding content gets displaced.

The rule is simple: **a shortcode callback always returns a value via ****return**. No echo, print, or direct HTML output outside a buffer.

The init hook is required

WordPress.org developers recommend hooking shortcode registration to init, not calling add_shortcode() directly in the body of functions.php. Without init, WordPress may not have finished initializing its internal mechanisms, and the shortcode simply won't fire.

Syntax and parameters

Function signature:

1add_shortcode( string $tag, callable $callback )

$tag, the shortcode name (without brackets). If two plugins register a shortcode with the same tag, the last one overrides the previous. To avoid conflicts, add a unique prefix: not [gallery], but [myplugin_gallery].

$callback, the name of the function that returns the content. You can pass a string with the function name, an anonymous function, or a class method (by passing an array [ $object, 'method_name' ]).

How to use shortcodes in the editor

In the WordPress editor, Gutenberg or Classic, a shortcode is inserted in square brackets. For our example:

[hello-world]

The full form with a closing tag also works:

[hello-world][/hello-world]

The second option is needed when content is passed between the tags (more on that below).

How to call a shortcode in PHP

In theme templates or plugins, shortcodes are executed via do_shortcode():

1echo do_shortcode( '[hello-world]' );

The function takes a string containing a shortcode and returns the result of its execution. It's useful when you need to embed shortcode output into PHP logic, for example, in header.php or inside a loop.

Passing data to a shortcode: two ways

Method 1: content between tags

Enclose the data between the opening and closing tags:

[hello-world]from John to Mary[/hello-world]

The callback receives the string from John to Mary via the second parameter $content:

1// Callback with content substitution between tags
2function hello_world_shortcode_callback( $attributes, $content, $tag ) {
3 return 'Hello ' . $content . ' World!';
4}
5// Registration
6function hello_world_shortcode() {
7 add_shortcode( 'hello-world', 'hello_world_shortcode_callback' );
8}
9add_action( 'init', 'hello_world_shortcode' );

Result: "Hello from John to Mary World!".

Method 2: attributes

Pass parameters directly in the opening tag:

[hello-world from="John" to="Mary"][/hello-world]

In the callback function, attributes are available via the first parameter $attributes, an associative array where keys match the attribute names:

1// Callback with reading shortcode attributes
2function hello_world_shortcode_callback( $attributes, $content, $tag ) {
3 return 'Hello from ' . $attributes['from'] . ' to ' . $attributes['to'] . '!';
4}
5// Registration
6function hello_world_shortcode() {
7 add_shortcode( 'hello-world', 'hello_world_shortcode_callback' );
8}
9add_action( 'init', 'hello_world_shortcode' );

Output: "Hello from John to Mary!". Attributes are better for structured data (price, URL, ID), while content between tags is better for arbitrary text.

In practice, you should immediately add shortcode_atts() to attributes, a built-in WordPress function that merges passed attributes with an array of default values. Without it, accessing $attributes['from'] will throw a PHP Notice if the user didn't specify the attribute. With shortcode_atts(), you always get a populated array: defined keys with fallback values, and unknown attributes are filtered out automatically.

Two ready-to-use shortcodes for WordPress

1. Automatic current year

The [current-year] shortcode inserts the current year on its own, no need to update the date in the footer every January:

1// Callback: returns current year via date('Y')
2function current_year_shortcode_callback( $attributes, $content, $tag ) {
3 return date( 'Y' );
4}
5// Registration of shortcode [current-year]
6function current_year_shortcode() {
7 add_shortcode( 'current-year', 'current_year_shortcode_callback' );
8}
9add_action( 'init', 'current_year_shortcode' );

Place [current-year] in the footer or post text, and the year will update automatically.

2. Registered user list

The [users-list] shortcode outputs the names of all site users as a bulleted list. Useful on a "Team" page or in admin reports:

1// Callback: gets all users and generates HTML list
2function my_custom_users_list() {
3 $users = get_users();
4 $list_html = '<ul>';
5 foreach ( $users as $user ) {
6 $list_html .= '<li>' . $user->data->display_name . '</li>';
7 }
8 $list_html .= '</ul>';
9 return $list_html;
10}
11// Registration of shortcode [users-list]
12function register_my_users_list_shortcode() {
13 add_shortcode( 'users-list', 'my_custom_users_list' );
14}
15add_action( 'init', 'register_my_users_list_shortcode' );

get_users() with no arguments returns all users. To limit the query by role, pass an array: get_users( [ 'role' => 'editor' ] ). The code goes into the active theme's functions.php or a custom plugin.

Video: creating shortcodes in WordPress

A short video on the topic, registering a shortcode from scratch, from writing the function to testing in the editor:

⁉️🤔 Frequent questions

What happens if two plugins register a shortcode with the same tag?

WordPress executes the one that loaded last. The first shortcode is overridden silently, with no errors. That's why you should always use a prefix in your shortcodes: [acme_gallery] instead of [gallery]. This eliminates conflicts with any other plugin or theme.

Can shortcodes be called inside other shortcodes?

Yes. If the first shortcode's callback returns a string containing the second shortcode's tag, WordPress processes the nested shortcode automatically. But don't forget about do_shortcode(): if you're building content manually and it contains shortcodes, run the string through do_shortcode() explicitly. The standard the_content behavior does this on its own, but a direct callback call does not.

Where should shortcode code live, in the theme or a plugin?

Custom shortcodes carry functionality, not appearance, so it's better to extract them into a separate plugin. Switching themes won't break the site: shortcodes will keep working. At minimum, use a child theme if you're certain you won't change the parent theme.

Are there restrictions on the tag name?

The name must consist of Latin letters, digits, and hyphens. Spaces, underscores, and special characters are not allowed. Recommended length is up to 20 characters, lowercase. Example: [my-plugin-shortcode].

How do you debug a shortcode if it outputs nothing?

First step, enable WP_DEBUG in wp-config.php. Second, check that the callback returns a value via return, not echo. Third, make sure the init hook is present. If there are no errors but the output is empty, add error_log() inside the callback and check the server logs.

What to remember about WordPress shortcodes

Shortcodes are a mature and stable mechanism. add_shortcode() hasn't changed in years: tag, callback, init hook, return instead of echo. Remember these four things, and any custom shortcode will work on the first try.

  • If you're outputting the year in the footer, grab the ready-made [current-year].
  • If you need a user list, [users-list] with a role filter.
  • If you're writing your own shortcode from scratch, always use a prefix and always use init.

For the next step, look into packaging a shortcode as a standalone plugin and submitting it to the WordPress.org directory, it's the best way to separate functionality from the theme and level up your developer profile.