Switching from Shortcodes to Blocks, part 1

Lately we’ve been putting a lot of effort into increasing the adoption rate of the WordPress block editor on campus. One of the larger hurdles in that process is how many custom shortcodes we have in use. The vast majority of these should be switched over to a corresonding block. In order to do so, we can indicate in the block configuration exactly how that transformation should be made. Unfortunately, at the time of this writing, the WordPress Developer Documentation for Blocks is very spotty, and there’s one major gap in the current functionality.

In this post I’d like to outline what I’ve learned as I wrote code allowing for the transformation of a custom shortcode to a block. I’m going to assume that you already have a basic understanding of how to write a block for the WordPress Block Editor. If not, you’ll probably want to work your way through some WordPress developer tutorials first.

There are three scenarios we’ll go over: a shortcode with no inner content/end tag, a shortcode with inner content which will be used as a block attribute, and a shortcode with inner content which will be used as inner blocks.

Shortcodes with no inner content

If the shortcode you’re working with has no end tag (no inner content) and the target block has no innerBlocks, the transformation is straightforward. This method is laid out relatively clearly in the docs, and I’ll briefly go over it here. Let’s use for this easy example the following shortcode:

[button link="https://example.com" text="Click Here"]

As part of your block registration, create a transforms key, with an object as the value. That object can have two keys from and to (only one is necessary). The value of those keys is an array of objects, with each object being a transformation configuration that might be used.

We’ll add a configuration object to the from array with the type: "shortcode". Here’s what it looks like so far:

registerBlockType('example/button', {
    ...
    attributes: {
        url: {
            type: 'string',
            ...
        },
        text: {
            type: 'string',
            ...
        },
    },
    transforms: {
       from: [
          {
             type: "shortcode",
          }
       ],
    },
    ...
}

Now, add the tag key with the value matching the shortcode. In our case, it’s the button shortcode.

...
type: "shortcode",
tag: "button",
...

Lastly, make an attributes key with an object; the object’s keys should match the block’s attributes. Each of those objects should have a key indicating the type, and a key named shortcode. The shortcode key should hold a function which will return the value of the block attribute. The first parameter of that function holds the shortcode attributes. If you want to transfer a shortcode’s attribute value directly to the block’s attribute value, it’s straightforward:

type: "shortcode",
tag: "button",
attributes: {
   text: {
      type: "string",
      shortcode: function(attributes) {
         return attributes.named.text;
      },
   url: {
      type: "string",
      shortcode: function( attributes ) {
         return attributes.named.link;
      },
   },
}

Or, if you prefer a newer code style:

type: "shortcode",
tag: "button",
attributes: {
   text: {
      type: "string",
      shortcode: attributes => attributes.named.text,
   url: {
      type: "string",
      shortcode: attributes => attributes.named.link,
   },
}

That’s it. When the page is transform from old editor to block editor, and the content is updated, the shortcode will be changed to the block with the attributes mapped as you’ve laid out.

“named” vs. “numeric”

It’s worth briefly noting that WordPress handles attributes with values slightly differently from attributes that are just a keyword. Any attribute that has a value can be found in attributes.named object as a key=value pair. Any attribute which is just a keyword can be found in the attributes.numeric array. So for example, given the following shortcode:

[button link="https://example.com" newwindow]

the attributes parameter in the shortcode function would look like:

{
   named: {
      link: "https://example.com"
   },
   numeric: [
      "newwindow"
   ],
}

Using shortcode inner content in a block attribute

To move on to the second scenario, let’s change example code a little; we’ll use the same block, but the following shortcode:

[button link="https://example.com"]Click Here[/button]

In this shortcode, we have inner content, and we’ll want to use that for the text attribute in the block. This method is not documented in the docs, but can be seen by trawling through the github repo. To access the shortcode inner content we have to look at the second parameter of the transforms.from.attributes[attribute].shortcode function. So we’ll change:

text: {
   type: "string",
   shortcode: function(attributes) {
      return attributes.named.text;
   },
}

to:

text: {
   type: "string",
   shortcode: function(attributes, data) {
      return data.shortcode.content;
   },

In an example I saw in the core code, autop and removep was used to clean up the formatting and prep the html for an attribute. To do that, you’ll need to first import those functions from the wp.autop library (this goes at the top of your code):

const { autop, removep } = wp.autop;

and then change return data.shortcode.content to return removep( autop( data.shortcode.content) ) If you do this, remember to add wp-autop as a dependency of your block’s javascript file when it’s enqueued.

Conclusion

Those are the first two scenarios, one which is documented well, and one not so much. Next up is part two: Using shortcode inner content as innerBlocks. Did this help at all? Let me know in the comments section, and thanks for reading!

Read Part 2

One Response to “Switching from Shortcodes to Blocks, part 1”

  1. Jason LeMahieu says:

    It’s me again. ๐Ÿ™‚

    I’m working through this now and once again, amazingly helpful content! Especially the part about how to even access the content that’s in [shortcode]here![/shortcode] as that is not in the documentation at all, as far as I can tell.

    I’m curious if you have any experience trying these methods with Blocks that are created with Advanced Custom Fields (https://www.advancedcustomfields.com/resources/blocks/), and if so, if you know of any pitfalls to look out for or tips. ACF Blocks really makes it easy to create blocks, but I’m afraid it might make a lot of the work of converting old content significantly more difficult. I think I’ve gotten to a point where I can pass a lot of the info into the block, but I’m still struggling with this text situation for situations like [shortcode]Doing Anything with THIS bit[/shortcode]

    (As a super tiny aside, you have this snippet in your code above: [button link=”https://example.com”]Click Here[/cta] – I’m guessing you wanted the same opening and closing tags?)

    Thanks again for such great content. I’ll be sure to browse your other articles for other tips! (And already went out andfound the prism code highlighter to use on my own site ๐Ÿ˜› )

Leave a Reply