Skip to content

[Block] Query Loop Excerpt does not preserve paragraph breaks for automatic excerpts #80380

Open

Description

@paaljoachim

Description

I first posted here: #49449

But this seems like a separate issue.
The problem is not only that inline HTML is stripped, but that the Post Excerpt block collapses multiple Gutenberg paragraph blocks into a single blob on the front end.

Excerpt block places everything into one blob.
Image


In my case, the source post contains distinct paragraphs, but the Query Loop / Post Excerpt output does not preserve paragraph breaks.
Could this be tracked as a separate bug or enhancement for paragraph preservation in automatic excerpts?


The post uses multiple paragraphs. See the following screenshot:

Image

It would be helpful to have the same paragraph breaks in the excerpt block as can be seen in the posts.
As it would keep a consistency between the initial look of a post and the excerpt uses elsewhere to give a preview that should look the same as the original post.

Step-by-step reproduction instructions

  1. Create a post with multiple paragraphs.
  2. Add the Query Loop turned into pattern into a page.
  3. Be sure to include the excerpt and a high word count.
  4. Notice it is in one blob and not in paragraphs as the post.

Screenshots, screen recording, code snippet

..

Environment info

Using Twenty Twenty Four and the newest Gutenberg plugin 23.5.3.

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure

Activity

  1. paaljoachim commented on Jul 16, 2026

    @paaljoachim
    ContributorAuthor

    With help from Perplexity AI. I got the excerpt to show the correct preview making it similar to the real posts by using this code.

    // Workaround for automatic Query Loop excerpts preserving Gutenberg paragraph breaks.
    if ( ! function_exists( 'ftu_excerpt_collect_paragraph_blocks_v2' ) ) {
    	function ftu_excerpt_collect_paragraph_blocks_v2( $blocks, &$paragraphs = array() ) {
    		foreach ( $blocks as $block ) {
    			if ( isset( $block['blockName'] ) && 'core/paragraph' === $block['blockName'] ) {
    				$html = trim( $block['innerHTML'] ?? '' );
    				$text = trim( wp_strip_all_tags( $html ) );
    
    				if ( $text !== '' ) {
    					$paragraphs[] = array(
    						'html' => $html,
    						'text' => $text,
    					);
    				}
    			}
    
    			if ( ! empty( $block['innerBlocks'] ) ) {
    				ftu_excerpt_collect_paragraph_blocks_v2( $block['innerBlocks'], $paragraphs );
    			}
    		}
    
    		return $paragraphs;
    	}
    }
    
    if ( ! function_exists( 'ftu_excerpt_trim_paragraph_html_to_words_v2' ) ) {
    	function ftu_excerpt_trim_paragraph_html_to_words_v2( $paragraphs, $max_words = 60 ) {
    		$output = '';
    		$count  = 0;
    
    		foreach ( $paragraphs as $paragraph ) {
    			$words = preg_split( '/\s+/', $paragraph['text'], -1, PREG_SPLIT_NO_EMPTY );
    			$word_total = count( $words );
    
    			if ( $count + $word_total <= $max_words ) {
    				$output .= $paragraph['html'];
    				$count  += $word_total;
    				continue;
    			}
    
    			$remaining = $max_words - $count;
    
    			if ( $remaining > 0 ) {
    				$trimmed_words = array_slice( $words, 0, $remaining );
    				$trimmed_text  = esc_html( implode( ' ', $trimmed_words ) ) . '&hellip;';
    				$output       .= '<p>' . $trimmed_text . '</p>';
    			}
    
    			break;
    		}
    
    		return $output;
    	}
    }
    
    add_filter( 'render_block_core/post-excerpt', function( $block_content, $block, $instance ) {
    	if ( empty( $instance->context['postId'] ) ) {
    		return $block_content;
    	}
    
    	$post_id = (int) $instance->context['postId'];
    	$post    = get_post( $post_id );
    
    	if ( ! $post instanceof WP_Post ) {
    		return $block_content;
    	}
    
    	$paragraphs = ftu_excerpt_collect_paragraph_blocks_v2( parse_blocks( $post->post_content ) );
    
    	if ( empty( $paragraphs ) ) {
    		return $block_content;
    	}
    
    	$max_words = 60;
    	if ( isset( $block['attrs']['excerptLength'] ) && is_numeric( $block['attrs']['excerptLength'] ) ) {
    		$max_words = (int) $block['attrs']['excerptLength'];
    	}
    
    	$excerpt_html = ftu_excerpt_trim_paragraph_html_to_words_v2( $paragraphs, $max_words );
    
    	if ( empty( $excerpt_html ) ) {
    		return $block_content;
    	}
    
    	$more_text = '';
    	if ( ! empty( $block['attrs']['moreText'] ) ) {
    		$more_text = sprintf(
    			'<a class="wp-block-post-excerpt__more-link" href="%s">%s</a>',
    			esc_url( get_permalink( $post_id ) ),
    			esc_html( $block['attrs']['moreText'] )
    		);
    	}
    
    	$show_more_on_new_line = ! isset( $block['attrs']['showMoreOnNewLine'] ) || $block['attrs']['showMoreOnNewLine'];
    	$wrapper_attributes     = get_block_wrapper_attributes();
    
    	$content  = '<div ' . $wrapper_attributes . '>';
    	$content .= '<div class="wp-block-post-excerpt__excerpt">' . $excerpt_html . '</div>';
    
    	if ( $more_text ) {
    		$content .= $show_more_on_new_line
    			? '<p class="wp-block-post-excerpt__more-text">' . $more_text . '</p>'
    			: $more_text;
    	}
    
    	$content .= '</div>';
    
    	return $content;
    }, 10, 3 );
    
    
  2. paaljoachim commented on Jul 16, 2026

    @paaljoachim
    ContributorAuthor

    I actually made a tutorial here:
    https://easywebdesigntutorials.com/how-to-keep-paragraph-breaks-in-wordpress-query-loop-previews/

    It would be great to get this fixed directly in WordPress though so that I would not need to use a code snippet.

  3. carolinan commented on Jul 17, 2026

    @carolinan
    Contributor

    Hi
    This is not a bug but the intended behavior of the excerpt, as documented in the code reference:
    https://developer.wordpress.org/reference/functions/the_excerpt/

    Note that the excerpt is a block of its own, it is not part of the query loop.

    To display a small amount of content without tags stripped, use the post content block and add the more block to the content.

  4. paaljoachim commented on Jul 17, 2026

    @paaljoachim
    ContributorAuthor

    Hei @carolinan

    Well I just want consistency in how a post is seen and how it is also visually seen in the preview.
    It is important to keep things consistent and this one is lacking. Adding a more block just complicates it.

  5. carolinan commented on Jul 18, 2026

    @carolinan
    Contributor

    I don't think I understand what you mean, what preview?

  6. t-hamano commented on Jul 18, 2026

    @t-hamano
    Contributor

    @paaljoachim So, you want to automatically display only the first few blocks of post content within a query loop block?

  7. paaljoachim commented on Jul 18, 2026

    @paaljoachim
    ContributorAuthor

    When for instance adding a word count of 60....if we count the 60 first words in a post it might look something like this post that contains paragraph formatting. The excerpt strips the formatting and does not show it like this.

    Image

    Another example.

    Here is a red outlined part of the beginning of a post.

    Image

    Lets say that I add 60 words and that it would actually cover the above outlined area which would be seen in the excerpt.
    Currently the excerpt would just show it like so:

    Query Loop and Latest Post Excerpt blocks turns multi-paragraph content into one flat blob. Here is the code snippet I used to fix it. I made an issue in the Gutenberg repository on Github here: #80380 The problem is that post previews of the text shows up in one blob. So using the Query Loop block or the Latest Post block makes it look like this:

    This is just one blob of text and a link. (not sure if the link would be added.)

    Consistency:
    The excerpt with its 60 words should show identical to the first 60 words of the post.
    Not as one blob of text with no formatting but the same visual at the post. Meaning if there are paragraph blocks in the post then these paragraph breaks should also be seen in the excerpt.

  8. t-hamano commented on Jul 18, 2026

    @t-hamano
    Contributor

    Consistency:
    The excerpt with its 60 words should show identical to the first 60 words of the post.
    Not as one blob of text with no formatting but the same visual at the post. Meaning if there are paragraph blocks in the post then these paragraph breaks should also be seen in the excerpt.

    What happens if a post contains blocks other than paragraph blocks at the beginning?

  9. paaljoachim commented on Jul 18, 2026

    @paaljoachim
    ContributorAuthor

    I would say that the excerpt should copy the first part of a post. But perhaps it should be defined differently then the first 60 words?
    Perhaps the excerpt should instead of defining words say copy an amount of blocks to be seen in the excerpt.
    Today we define through words. What if one wants to show a cover block with a video in it as part of the excerpt? What if one wants a direct preview of the post with the first 5 blocks as part of the excerpt? One should then define: How many blocks to show in the excerpt as a more consistent way of showing the beginning of a post. It would be more true to how the post is seen.

    Now extending this...
    Each post has the possibility to add a text excerpt. This excerpt could also be more advanced defined to where the user adds blocks into the excerpt box of this single post. Yes some might just want an unique text excerpt seen while others might want to add an image etc as an unique excerpt.

    So to keep with consistency. Showing excerpt can be a kind of preview of the beginning of the post with blocks. OR one could make an unique excerpt through the post and add blocks to it. This way the excerpt is not only focused on text but on a more visual experience.

  10. paaljoachim commented on Jul 18, 2026

    @paaljoachim
    ContributorAuthor

    I got some help with a better framing of what I am working on explaining....

    I think this points to a broader need for a more block-aware excerpt model.
    Right now, automatic excerpts are based on word count, which can flatten the visual structure of Gutenberg content and turn multiple paragraph blocks into one blob.
    If the post starts with paragraph blocks, the excerpt should preserve those paragraph breaks.
    Even better, excerpts could offer a block-based mode, where the preview shows the first N blocks instead of only the first N words.
    That would make previews feel more like the post itself, rather than a plain text summary.
    A manual excerpt could also support blocks, so users who want a richer preview can intentionally add text, images, or other blocks.

  11. t-hamano commented on Jul 18, 2026

    @t-hamano
    Contributor

    Even better, excerpts could offer a block-based mode, where the preview shows the first N blocks instead of only the first N words.

    I think this is just making things more difficult. We should simply consider how many blocks to display in the Content block.

    This is an example of a filter that displays only the first three blocks of a post if the Content block has a specific CSS class. Although I haven't tested it, if we are to move forward with this issue, it means we should discuss whether to add an option to the Content block that performs the equivalent of the following filter.

    add_filter( 'render_block_data', function ( $parsed_block ) {
    	if ( 'core/post-content' === ( $parsed_block['blockName'] ?? '' ) ) {
    		$class_name = $parsed_block['attrs']['className'] ?? '';
    		if ( in_array( 'is-limited-content', preg_split( '/\s+/', $class_name ), true ) ) {
    			add_filter( 'the_content', 'my_limit_post_content_blocks', 8 );
    		}
    	}
    	return $parsed_block;
    } );
    
    function my_limit_post_content_blocks( $content ) {
    	remove_filter( 'the_content', 'my_limit_post_content_blocks', 8 );
    	$blocks = array_filter(
    		parse_blocks( $content ),
    		static function ( $block ) {
    			return ! empty( $block['blockName'] );
    		}
    	);
    	return serialize_blocks( array_slice( $blocks, 0, 3 ) );
    }
  12. carolinan commented on Jul 18, 2026

    @carolinan
    Contributor

    Again, unsing the post content block in the template and the more block after your chosen blocks solves this.

    What if on 4 posts, your count of 8 blocks work, but on the 5th post you decide you need one more block. The existing method already works for this.

    Adding the more block is not more complicated than entering a custom excerpt. Yes, you have to edit each post.

  13. paaljoachim commented on Jul 18, 2026

    @paaljoachim
    ContributorAuthor

    I see the point about using the Content block plus a More block for a manual, controlled preview.

    My concern is that the current Excerpt block behavior still flattens the automatic preview, so the issue here is not just “how to manually build a preview,” but that the default preview output does not preserve the structure of the post when it is generated automatically.
    Because of that, I still think this belongs in the Query Loop / Post Excerpt behavior, since the goal is to keep the excerpt visually consistent with the opening part of the post without requiring each post to be manually configured.

    If the preferred solution is a Content block option that can show only the first N blocks, that would also address the consistency problem — but it would still need to be an automatic, block-aware preview, not just a manual workaround.

  14. carolinan commented on Jul 18, 2026

    @carolinan
    Contributor

    That is where our opinions differ, the goal of the excerpt in WordPress has historically not been to keep visually consistent with the post and this is documented in the code reference I linked.

    This request from 16 years ago has not recieved much attention from users or contributors: https://core.trac.wordpress.org/ticket/12084

  15. CookieDarb commented on Jul 20, 2026

    @CookieDarb
    Contributor

    Hi everyone,

    I agree that the current behaviour is intended rather than a bug. With that in mind, I had worked on a PR to add an opt-in toggle to "preserve formatting," which keeps the default behaviour exactly as it is but selectively allows paragraph blocks and breaks to be preserved if the user chooses. #80419

    However, after going through the discussion I believe that using the Content block with the More block is the preferred approach, still for now I've left the PR open in case an opt-in solution is desired.

  16. paaljoachim commented on Jul 20, 2026

    @paaljoachim
    ContributorAuthor

    Having a toggle in the Excerpt block seems like a good solution. Since the Excerpt block is already part of the Query Loop, it would be much easier for users to preserve formatting with a simple option. For a beginner, that would be straightforward. The alternative of using a More block means editing each individual post, which adds extra steps and complexity. I was hoping for one simple method that works automatically.

  17. github-actions commented on Aug 5, 2026

    @github-actions

    Help us move this issue forward. This issue is being marked stale since it has no activity after 15 days of requesting more information. Please add info requested so we can help move the issue forward. Note: The triage policy is to close stale issues that need more info and no response after 2 weeks.

  18. added
    [Status] StaleGives the original author opportunity to update before closing. Can be reopened as needed.
    on Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Block] Query LoopAffects the Query Loop Block[Status] In ProgressTracking issues with work in progress[Status] Needs More InfoFollow-up required in order to be actionable.[Status] StaleGives the original author opportunity to update before closing. Can be reopened as needed.[Type] BugAn existing feature does not function as intended

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions