Remove Empty Paragraphs from WordPress Post Content with the_content Filter

WordPress runs the content of every post through a set of filters before outputting it with the_content(). One of those filters — wpautop — wraps blocks of text in <p> tags and adds line breaks. It is useful for simple text content, but when posts are built in the Gutenberg editor or with page builders, it often generates empty <p> </p> and <p></p> elements — invisible spacer paragraphs that add unwanted whitespace, break CSS selectors, and inflate the DOM. These empty elements also affect SEO: duplicate or near-duplicate whitespace paragraphs can confuse crawlers and make the content/code ratio worse than it needs to be. The fix is a single filter on the_content that strips empty paragraph tags after wpautop has finished, using a regex that matches paragraphs containing only whitespace or non-breaking space characters.

Problem: The Gutenberg editor or a page builder is generating empty <p></p> and <p> </p> tags in post content, adding invisible whitespace, breaking layouts, and inflating the DOM.

Solution: Add a filter on the_content that runs after wpautop and removes paragraphs containing only whitespace or &nbsp; entities. Use force_balance_tags() first to avoid stripping paragraphs with unclosed tags.

<?php
add_filter( 'the_content', 'remove_empty_paragraphs', 20 );

function remove_empty_paragraphs( $content ) {
    // Balance any unclosed tags first to avoid removing legitimate content
    $content = force_balance_tags( $content );

    // Remove paragraphs that contain only whitespace or   entities
    return preg_replace( '/<p>(?:\s|&nbsp;| )*?<\/p>/i', '', $content );
}

If you also need to strip empty paragraphs from ACF WYSIWYG fields or widget text, apply the same function to those filters:

<?php
// ACF WYSIWYG fields
add_filter( 'acf/format_value/type=wysiwyg', 'remove_empty_paragraphs', 20 );

// Text widgets
add_filter( 'widget_text_content', 'remove_empty_paragraphs', 20 );

NOTE: The filter priority is set to 20 so it runs after wpautop (priority 10) and after most other content filters (default priority 10). If you are using a page builder that registers its own content filters at a higher priority, you may need to raise the priority further — try 99 if empty paragraphs still appear after applying the filter.