I've written a custom plugin, ta-intentclicks which is used as a shortcode:

[ta-intentclicks count="3" category="SEC-EDR"...]

Within this shortcode I'd like to use another shortcode that I can use as a helper. For example; in one of the PHP templates within my plugin.

[ta-intentclicks-link url="$list_item['link']"]Visit website[/ta-intentclicks-link]
[ta-intentclicks-link url="$list_item['link']"]<img src="foo" />[/ta-intentclicks-link]

Which would output this:

<a href="<the URL>" rel="sponsored" target="_blank" class="icp-list-link">Visit website</a>
<a href="<the URL>" rel="sponsored" target="_blank" class="icp-list-link"><img src="foo" /></a>

Here's a quick directory snapshot to help illustrate my question. enter image description here

The plugin entry point is includes/class-ta-intentclicks.php which defines the shortcode and runs it, calling the Layout class along the way.

class TaIntentClicks {

    function __construct() {
        add_shortcode('ta-intentclicks', array($this, 'run'));

    function run($attributes = []) {
        ... do some stuff
        return $this->layout->render($response, $layoutAttributes, $dataAttributes);


class TAIntentClicksLayout {
    function parse($stuff, $template) {

        $output = '';
        include $template;
        $output = ob_get_contents();
        return $output;

    function render($response, $layoutAttributes, $dataAttributes) {

        return $this->parse(

I've seen examples of people calling "do_shortcode" to execute a shortcode, but I'm unclear where the shortcode function goes in my case, OR where to place the "do_shortcode" call.

Can anyone offer guidance here? This is my first plugin and I'm a bit lost as to how and implement this functionality. Thanks in advance.

3 Answers 3


If I understand it correctly, inside the shortcode template, you could do something like this:

echo do_shortcode( '[ta-intentclicks-link url="' . esc_url( $list_item['link'] ) . '"]Visit website[/ta-intentclicks-link]' );

But then, instead of having to use do_shortcode(), you could actually simply call the shortcode callback from the (shortcode) template ( which then eliminates the need to find and parse shortcodes in the content or the first parameter for do_shortcode() ):

  • If you registered the shortcode like so:

    add_shortcode( 'ta-intentclicks-link', 'my_shortcode' );
    function my_shortcode( $atts = array(), $content = null ) {
        $atts = shortcode_atts( array(
            'url' => '',
        ), $atts );
        if ( ! empty( $atts['url'] ) ) {
            return sprintf( '<a href="%s" target="_blank">%s</a>',
                esc_url( $atts['url'] ), esc_html( $content ? $content : $atts['url'] ) );
        return '';
  • Then in the shortcode template, you could simply call the my_shortcode() function above:

    echo my_shortcode( array(
        'url' => $list_item['link'],
    ), 'Visit website' );

But if that's not what you meant, or if you had a shortcode in a shortcode like [caption]Caption: [myshortcode][/caption], then as said in the Shortcode API on the WordPress Codex website:

If the enclosing shortcode is intended to permit other shortcodes in its output, the handler function can call do_shortcode() recursively:

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

... your run() function (or the shortcode callback) would need to capture the second parameter passed to the function, i.e. $content as you could see above, and once you have that parameter (or its value), you can then call do_shortcode().

(With the above "caption" shortcode example, the $content is the Caption: [myshortcode].)

So for example, your run() function be written like so…

// 1. Define the $content variable.
function run( $attributes = [], $content = null ) {
    // ... your code (define $response, $layoutAttributes, $dataAttributes, etc.)

    // 2. Then do something like so:
    return $this->layout->render( $response, $layoutAttributes, $dataAttributes ) .
        do_shortcode( $content );

But if you need further help with that, do let me know in the comments (and preferably, please show the code in your template and explain the variables like the $response and $layoutAttributes).

function parse($stuff, $template) {
    $output = '';
    include $template;
    $output = ob_get_contents();
    $output = do_shortcode($output); // This line will parse shortcodes from $template
    return $output;

To add some details: your main shortcode, [ta-intentclicks], I assume you're placing that in the Page/Post content. WordPress runs do_shortcode on the_content so that works as expected.

do_shortcode does not recursively run shortcodes, meaning, nested shortcodes just display as plain text wrapped in brackets / they do not render the shortcode content. So, you need to run do_shortcode again on the content of your shortcode to process the next "level" of the shortcode. Likewise, if you have a shortcode inside of that shortcode inside of [ta-intentclick], you'd need to run do_shortcode a second time (third time if you count WP's core running of it).

I'm not sure that this behavior is specifically documented, but if you read through the do_shortcode source code here it's apparent it doesn't handle sub-shortcodes.


Why don't you use this simple code to do that? I mean, it doesn't require shortcode inside shortcode.

function links( $atts, $content = null ){
    $args = shortcode_atts( array(
        's' => '', //name
        'l' => '', //link
    ), $atts);

    return '<a href="'. $args['l'] .'">'. $args['s'] .'</a>';
add_shortcode ('link', 'links');

you can write [link s="NAME" l="LINK"] and it is more simple.

  • The main reason is that I need to conditionalize the inclusion of the link. So for a given piece of text, one of my admins can include a property that says "this should be a link, or this should not be a link". Also, it's easy to link a string of text, but harder to wrap a link around a dynamic image tag. Commented Apr 16, 2021 at 18:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.