Skip to content

Tag: fixing WordPress

  • Remove Unwanted Paragraph Tags Around Shortcodes in WordPress Widgets

    If you’ve ever created a custom WordPress shortcode and used it in a widget, you’ve probably encountered a frustrating issue: WordPress automatically wraps your shortcode output in empty <p> tags. These unwanted paragraph tags can break your layout, add unnecessary spacing, and make your carefully crafted HTML look messy.

    In this article, I’ll show you exactly why this happens and how to fix it with the correct filter priority.

    The Problem

    When you create a widget (particularly with the block editor’s Shortcode block) and add a simple shortcode like:

    [custom_shortcode src="#" size="medium"]

    You expect clean HTML output. Instead, WordPress wraps your content in paragraph tags like this:

    <section id="block-3" class="widget">
        <p></p>
        <div class="your-container-class" data-size="medium">
            <!-- Your shortcode content -->
        </div>
        <p></p>
    </section>

    Those empty <p></p> tags add unwanted margin and spacing, breaking your carefully designed layout.

    Why This Happens

    WordPress has a built-in filter called wpautop that automatically converts line breaks into HTML paragraph tags. This is great for blog content, but it becomes problematic when working with shortcodes that output block-level elements like <div> tags.

    The block editor’s Shortcode block processes content through several filters, including wpautop, which tries to be helpful by wrapping your content in paragraph tags. Even though your shortcode outputs a complete <div> element, WordPress still adds those <p> tags around it.

    The Solution: Custom Filter with Proper Priority

    The fix involves creating a custom filter that strips out these unwanted tags. But here’s the critical part: filter priority matters.

    Many developers try using WordPress’s built-in shortcode_unautop() function at the default priority, but this doesn’t work reliably with block-based widgets. The key is to run your cleaning filter at a very high priority (999) so it executes after all other filters have finished processing the content.

    Here’s the complete solution:

    class Your_Shortcode_Class {
    
        public function __construct() {
            add_shortcode( 'your_shortcode', [ $this, 'render_shortcode' ] );
    
            // Ensure shortcodes work in widgets
            add_filter( 'widget_text', 'do_shortcode', 11 );
    
            // Handle block-based widgets (Gutenberg)
            // Process shortcodes first, then clean up <p> tags at very high priority
            add_filter( 'widget_block_content', 'do_shortcode', 11 );
            add_filter( 'widget_block_content', [ $this, 'clean_widget_shortcode_output' ], 999 );
            add_filter( 'widget_text', [ $this, 'clean_widget_shortcode_output' ], 999 );
        }
    
        /**
         * Clean widget shortcode output by removing empty <p> tags
         *
         * @param string $content Widget content.
         * @return string Cleaned content.
         */
        public function clean_widget_shortcode_output( $content ) {
            // Only process if your shortcode container is present in the output
            if ( strpos( $content, 'your-container-class' ) === false ) {
                return $content;
            }
    
            // Remove empty <p> tags (including those with whitespace)
            $content = preg_replace( '/<p>(\s|&nbsp;)*<\/p>/i', '', $content );
    
            // Remove <p> tags that wrap our container divs
            $content = preg_replace( '/<p>(\s*)(<div[^>]*your-container-class[^>]*>)/i', '$2', $content );
            $content = preg_replace( '/(<\/div>)(\s*)<\/p>/i', '$1', $content );
    
            // Remove stray <br> tags around containers
            $content = preg_replace( '/<br\s*\/?>(\s*)(<div[^>]*your-container-class)/i', '$2', $content );
            $content = preg_replace( '/(<\/div>)(\s*)<br\s*\/?>/i', '$1', $content );
    
            return $content;
        }
    
        public function render_shortcode( $atts ) {
            // Your shortcode rendering logic
            return '<div class="your-container-class">Your content</div>';
        }
    }

    Understanding Filter Priority

    WordPress filters execute in order of priority, from lowest to highest numbers:

    1. Priority 10 (default): Most filters run here, including wpautop
    2. Priority 11: The do_shortcode filter processes shortcodes
    3. Priority 999: Our cleanup filter runs last

    If you run your cleanup filter at priority 10, it will execute before wpautop adds the paragraph tags, making it ineffective. By setting it to 999, we ensure it runs after all other filters have finished, giving us the final say on the output.

    Breaking Down the Regex Patterns

    The cleaning function uses multiple regex patterns instead of trying to match everything at once:

    1. Remove empty paragraphs: /<p>(\s|&nbsp;)*<\/p>/i catches <p></p>, <p> </p>, and <p>&nbsp;</p>
    2. Remove opening <p> before your container: /<p>(\s*)(<div[^>]*your-container-class[^>]*>)/i strips the opening paragraph tag that appears before your div
    3. Remove closing </p> after your container: /(<\/div>)(\s*)<\/p>/i removes the closing paragraph tag after your closing div
    4. Clean up break tags: Two patterns remove <br> tags that appear before or after your container

    This multi-pattern approach is more reliable than trying to match the entire wrapped structure in a single regex.

    Implementation Tips

    1. Replace the identifier: Change your-container-class to whatever unique class or attribute your shortcode uses. This ensures the filter only processes your specific shortcode output.
    2. Add to both filters: Apply the cleanup to both widget_block_content (Gutenberg blocks) and widget_text (classic text widgets) to cover all use cases.
    3. Test thoroughly: Check your widgets in different contexts – sidebars, footers, etc. – to ensure the cleanup works everywhere.
    4. Don’t forget do_shortcode: Make sure you’re enabling shortcode processing in widget_block_content at priority 11, before your cleanup runs at priority 999.

    Testing the Fix

    After implementing this solution, inspect your widget output in the browser’s developer tools. You should see clean HTML like this:

    <section id="block-3" class="widget">
        <div class="your-container-class">
            <!-- Your shortcode content -->
        </div>
    </section>

    No more empty paragraph tags, no more unexpected spacing!

    Conclusion

    WordPress’s automatic paragraph insertion is helpful for blog content but can wreak havoc on shortcode output in widgets. The solution isn’t just about adding a filter – it’s about understanding filter priority and ensuring your cleanup code runs at the right time.

    By running your cleanup filter at priority 999, you guarantee it executes after all other filters, giving you complete control over the final output. This simple change can save hours of debugging and ensure your shortcodes render exactly as intended.

    Have you encountered this issue in your WordPress projects? Let me know in the comments how you solved it!

  • WordPress 404 Fix: Why You Just Need to “Resave” Your Permalinks

    If you’ve ever moved a WordPress site, updated a major plugin, or added a Custom Post Type, you’ve probably encountered the dreaded “404 Not Found” error on pages that you know exist.

    And if you’ve searched for the fix, you know the most common, and seemingly illogical, solution: Go to Settings > Permalinks and simply click “Save Changes,” without actually changing anything.

    Why does this magic button press work? It’s not magic; it’s a necessary synchronization between WordPress and your web server. Here is a history of the issue, a simple explanation of why it happens, and how to fix it for good.


    Part 1: The Core Problem—A Mismatch in Maps

    To understand the fix, you first have to understand the job of a permalink.

    Permalinks: The Simple Address

    A “permalink” is just the permanent, clean, and readable URL (like /my-awesome-post/). When a visitor types this into their browser, two main things have to happen:

    1. The Server’s Job (Nginx/Apache): The server has to receive the request and figure out that the clean-looking URL actually needs to be processed by one specific file: the main WordPress script (usually `index.php).
    2. WordPress’s Job: Once WordPress takes the request, it looks at its internal rules to determine exactly whichpiece of content (Post ID 123, Custom Post Type “Sponsored Content”, etc.) matches the URL.

    The technical glue that makes step 1 work is a set of instructions called Rewrite Rules.

    The History of the Problem

    When WordPress was first developed, the default URLs were ugly, using IDs and question marks (e.g., ?p=123). As websites became more advanced and SEO became critical, WordPress needed a clean URL structure.

    The permalink feature was created to solve this. It allowed WordPress to generate those complex Rewrite Rulesautomatically. The problem that has persisted is that WordPress is not always perfect at telling the web server or its own database that a new rule is needed.

    This is where the mismatch happens:

    • WordPress knows the page exists.
    • The Server’s Routing Map (the Rewrite Rules) is either missing the rule or using an outdated version.

    Part 2: The Role of Custom Post Type Plugins

    While WordPress core sometimes causes these issues, they are far more common when using third-party tools to create custom content structures.

    Plugins like WCK (WordPress Creation Kit)Custom Post Type UI, or similar builders are fantastic for easily adding new content types (like “Sponsored Content,” “Case Studies,” or “Products”) without writing code.

    However, they introduce an additional step in the communication chain:

    The ActionThe Expected ResultThe Potential Breakdown
    You create a new CPTThe CPT Plugin tells WordPress to register a new route.The plugin fails to send the final signal to save the updated Master Route List.
    You edit a CPT slugThe plugin updates the page, but doesn’t touch the routes.WordPress core doesn’t get the signal to check the routes, and the new URL is stuck in limbo.

    This creates a communication gap. The plugin is being “too efficient”—it only updates the content, not the site-wide map. The result is the same: the server looks at the old, incomplete map and throws a 404 Not Found error.


    Part 3: Why the “Save Changes” Button Works

    When you click “Save Changes” on the Permalinks screen, you are performing a critical synchronization step known as “Flushing the Rewrite Rules.”

    Think of your website as a Librarian (WordPress) with a Master Catalog (the Database).

    The Fix (Why it Works)

    By clicking the “Save Changes” button, even if you change nothing, you are essentially bypassing the complex, multi-layered system and giving WordPress this simple, non-negotiable command:

    “Discard the current, potentially stale Rewrite Rules, recalculate the entire list from scratch based on everything I have—all my posts, pages, and custom types—and then force that fresh list into the database and server configuration.”

    This simple action forces synchronization and instantly corrects the outdated routing map, clearing up the 404 errors caused by core glitches or plugin communication issues.


    Part 4: The Troubleshooting Steps

    The “resave” trick works over 90% of the time. If it doesn’t, here are the next steps to ensure the fix is permanent.

    1. The Standard Permalink Fix (The First Step)

    1. Navigate to Settings > Permalinks in your WordPress dashboard.
    2. Note your current setting (e.g., “Post Name”).
    3. Click the Save Changes button.

    2. The Cache Flush (The Second Step)

    Sometimes the fix works, but your visitors (or you) are seeing an older version of the site saved by a caching system.

    • Clear all forms of caching on your site: Server-level cache, WordPress plugin cache (WP Rocket, LiteSpeed, etc.), and CDN cache (Cloudflare, etc.).

    3. The Plugin Conflict Check (The Root Cause)

    If you find you have to resave permalinks constantly, the CPT plugin itself might be the conflict point.

    1. Deactivate all plugins except the one causing the issue (e.g., your CPT builder).
    2. Try to reproduce the 404 error (e.g., by changing a page slug).
    3. If the error stops, reactivate your plugins one by one, checking for the 404 each time, until you isolate the offender.

    By understanding that this is simply a synchronization error, often exacerbated by the tools we use for efficiency, you can confidently explain the “magic” of the resave button and troubleshoot any future routing headaches.

  • Set Tag Order WordPress Plugin

    A few months ago, I was asked to solve what I thought was a simple problem. The question was, “Can we set the display order of tags?” It seemed like a very benign question until I started to dig into it and discovered that it’s, in fact, not a simple question at all. WordPress, by default, sorts tags alphabetically when you use the get_tags() function.

    The problem I was asked to create a solution for was to allow the editor to specify the order that tags are rendered on the corresponding post page. It was way more involed thatn I had anticipated but ultimately, I created a plugin that works with both the block editor and classic editor and should also work with any theme that displays tags using the get_tags() function.

    I’ve attached some screenshots to help illustrate but the plugin can be downloaded directly from my GitHub. I have made this public and welcome any and all feedback or feature requests!

    This plugin is now also listed in the WordPress plugin directory! You can download it from WordPress.org here or add it directly from your WordPress installation.