8.8 Testing Shortcodes in the Dev Server
Alright, let’s get our hands dirty. You’ve just written a shortcode, either by bending one of WordPress’s built-in marvels to your will or by forging a completely custom one from raw PHP. You feel like a digital wizard. Don’t get cocky. The single most important step, the one that separates the pros from the amateurs who break their live site on a Tuesday afternoon, is testing. And I mean real testing, not just glancing at it and saying “yep, looks good.”
The WordPress development server—be it Local, Flywheel, XAMPP, or whatever you’ve got running on localhost—is your personal sandbox, your testing ground, your laboratory. This is where you get to blow things up without consequence. The key here is to test like a maniac. Don’t just test the perfect scenario. What happens if you feed your [latest_post] shortcode a negative number for posts? What if you leave the title attribute blank in your [fancy_quote] shortcode? You must become a chaos monkey, intentionally trying to break your own code. You will be shocked—and then eventually proud—by what you can break.
The Art of the Brutal Test Case
Don’t just pop the shortcode into a page and call it a day. You need to create a dedicated testing page. I literally have a page in my dev install called “Shortcode Testing Dump” that I use for exactly this. It’s a beautiful mess of broken layouts and bizarre output, and it’s saved me from public embarrassment more times than I can count.
Here’s a sample of what my test page content looks like for a hypothetical [user_greeting] shortcode that accepts a user_id attribute:
<h3>Normal Operation</h3>
[user_greeting user_id="1"]
<h3>Edge Cases and Malicious Compliance</h3>
[user_greeting] // No attributes at all
[user_greeting user_id="999999"] // Non-existent user ID
[user_greeting user_id="not_a_number"] // This is just rude
[user_greeting user_id=""] // Empty attribute
[user_greeting user_id="1" nonsense_attr="whyWouldYouDoThis"] // Extra junk
This approach systematically runs your shortcode through the gauntlet. The first one is what you expect users to do. The rest are what users will actually do. Your shortcode’s PHP code must handle these gracefully, either by failing silently with a sensible default or by returning a helpful error message that only logged-in admins can see (because you don’t want to spit errors on your live site).
Viewing the Source is Non-Negotiable
Rendering the page and seeing “Hello, admin” is only half the job. You must right-click and view the page source. Why? Because your shortcode might be outputting the right text but wrapping it in twelve nested <div> tags, or worse, it might be accidentally echoing unwanted information before WordPress starts sending headers.
Look for errant whitespace—a single space or newline outside of your PHP tags can cause the dreaded “headers already sent” error, which is about as fun as a root canal. This is why, in the code example below, I’m so pedantic about not having any whitespace after the closing ?> tag. In fact, the best practice is to just omit the closing PHP tag entirely in shortcode functions to prevent this exact nightmare.
A Real-World Example: Let’s Break It
Let’s say you have a shortcode that fetches a post title. Here’s the first pass a lot of people write, and it’s riddled with problems:
function my_bad_shortcode( $atts ) {
$atts = shortcode_atts( array(
'id' => 1,
), $atts );
$post = get_post( $atts['id'] );
return $post->post_title;
}
add_shortcode( 'get_title', 'my_bad_shortcode' );
Seems simple, right? Now, let’s run our test cases from above. What happens if id="999999"? get_post returns null, and trying to get ->post_title on a null object gives you a nasty PHP warning. What if id="not_a_number"? The 'id' => 1 default won’t save you; shortcode_atts doesn’t cast types, so you’re still passing a string to get_post. It might fail weirdly. This code is brittle.
Here’s the more robust, “I’ve been burned before” version:
function my_good_shortcode( $atts ) {
// shortcode_atts handles the merging, but we need to sanitize the ID
$atts = shortcode_atts( array(
'id' => 1,
), $atts );
// Force the ID to be an integer. Absint is your friend.
$post_id = absint( $atts['id'] );
// Check if this post actually exists and is published
$post = get_post( $post_id );
if ( ! $post || $post->post_status !== 'publish' ) {
return ''; // Fail silently, or return a message for admins
}
// Now we can safely use the object
return esc_html( $post->post_title );
}
add_shortcode( 'get_title', 'my_good_shortcode' );
See the difference? We sanitize the input, validate the output from get_post, and escape the text before returning it. This is what testing teaches you. It forces you to think defensively. The dev server is where you find these cracks in your logic before they become gaping holes on your production site. Now go break something. On purpose.