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