I occasionally dabble in custom WordPress themes on my wife’s behalf and in furtherance of her vast eBook empire. As part of her current theme, she wanted some shortcodes to do neat things like having a multi-column section of a blog post. That way, she can write:
[columns]
[column]First column here![/column]
[column]Second column here![/column]
[/columns]
instead of the actual mess of HTML and CSS required to make such magic happen. Plus, if we ever decide to change the markup we’re using to generate columns, we can just change the implementation of the shortcode instead of going and updating a whole bunch of blog posts. Cool feature!
Defining a shortcode
In your theme’s functions.php
, you need to do (at minimum) two things:
- Create a function that replaces the shortcode with its actual content.
- Register your shortcode with WordPress.
This part is straightforward. Say we wanted to create a [contentbox]
shortcode to automatically wrapped its contents into an <aside>
. That could look something like:
We could use it this way:
[contentbox align="right"]Here's a nice right-aligned piece of content![/contentbox]
When included in a post, that would become:
<aside class='content-box right'>Here's a nice right-aligned piece of content!</aside>
(CSS to make it actually look like a box and aligned right is left as an exercise for the reader.)
The evil that lies within
There’s a problem. WordPress has an ancient, vile, and notorious function in place which is designed to make users’ lives easier and developers’ lives miserable. This function is called wpautop
and I hate it passionately.
The idea behind wpautop
is simple: convert all double linebreaks into paragraph elements, and tack on <br>
tags to whatever’s left. This lets an average user type text into the editor and have it converted to HTML even without using WordPress’ visual editor. The problem is that wpautop
doesn’t know anything about shortcodes, so if our user splits our shortcode into several lines (such as the [columns]
example at the top of this post), wpautop
comes through and sprays <br>
tags everywhere. Incidentally, <br>
stands for “break”, which is exactly what wpautop
is designed to do to nice complex shortcodes.
This problem has been known for ages – it was reported at least 9 years ago as of this writing, was still allegedly being discussed by WordPress devs 7 months ago, and there is no fix. Ridiculous.
Rescuing the shortcodes
There are at least four ways to deal with the problem of wpautop
murdering our shortcode content:
Option 1: Change do_shortcode
filter priority
While loading a page, WordPress executes a long list of “filters”, which are just functions that operate on the post’s content. wpautop
is one such filter, as is do_shortcode
. The latter is what ends up calling our own custom shortcode functions, replacing the shortcodes with actual content in the post. In WordPress core, wpautop
runs at priority 10 and do_shortcode
runs after that, at 11. So one way to thwart wpautop
is to change do_shortcode
‘s priority to 9, meaning our shortcodes will be resolved first and hopefully wpautop
won’t mangle the output too badly.
This isn’t a great solution because it can potentially break all kinds of other themes or plugins (which might rely on WordPress’ default filter order) in subtle ways. Until today, I was using this approach for my wife’s site theme, but then we started switching her shopping cart to WooCommerce and discovered that my hack broke the text on the “proceed to checkout” button (because wpautop
vomited a <br>
randomly into the button text).
So if you take this route, expect occasional oddities with other plugins. This is not a well-behaved solution.
Option 2: Disable wpautop
entirely
I love this option because I hate wpautop
. However, this will likely break even more themes/plugins than option 1, and also users may not get the expected results if they’re composing posts in the text editor without using a sensible text parser like Markdown. Still, if you want to do it, just put these two lines into functions.php
:
remove_filter( ‘the_content’, ‘wpautop’ );
remove_filter( ‘the_excerpt’, ‘wpautop’ );
Option 3: Disable wpautop
on a post-by-post basis
You can install a plugin to disable wpautop
only on certain posts–in our case, the ones with shortcodes on it.
I like how the plugin description explains its existence by saying “Back in the day, when the wpautop filter was really sucky…” What, you mean like yesterday?
Option 4: Hide the shortcodes behind a false bookcase
Like squirreling away guns or hiding Jews from the Nazis, this approach pretends that shortcodes don’t exist until after wpautop
runs. It’s the most complicated, but it’s probably also the best because it lets wpautop
commit whatever debauchery it wants on the post and then puts our precious shortcodes back in place unmolested. There are two steps:
- Register a filter before
wpautop
to search the post content for any shortcodes and replace them with random strings. - Register a filter after
wpautop
but beforedo_shortcode
to switch out the random strings for the original shortcode text.
Easier said than done, because finding complex shortcodes is no walk in the park. Fortunately, I’ve done the hard work for you!
When included in your functions.php
file, the above code will:
- Search for any shortcodes in your post/page content.
- Once a shortcode opening tag is found, attempt to locate its corresponding closing tag.
- Replace the entire shortcode contents in the post with a random string.
- After
wpautop
has run, replace the random strings with the original shortcode markup.
After that, do_shortcode
should run and evaluate your shortcodes normally.