Using Shortcodes in the_excerpt

I was working on a project for a client that requested using shortcodes in the_excerpt:

From codex.wordpress.org:

Displays the excerpt of the current post with […] at the end, which is not a “read more” link. If you do not provide an explicit excerpt to a post (in the post editor’s optional excerpt field), it will display an automatic excerpt which refers to the first 55 words of the post’s content. Also in the latter case, HTML tags and graphics are stripped from the excerpt’s content. This tag must be within The Loop.

You’ve probably encountered this functionality when you’ve done a search on a WordPress site or viewed an archive, the post will often end in “…”

From a programming standpoint, there was a point in time where if you wanted to have shortcodes used in an excerpt for WordPress, all you had to do what include this in your code1

<br />
add_filter( 'the_excerpt', 'do_shortcode');<br />

However, I found out that at some point in the last few years since I tested the above system, the way in which WordPress process shortcodes has changed. I poked around a little bit in the WordPress TRAC, but didn’t see anything that would explain it. I suspect it might be a filter order, but I’m not sure and I decided that investigating the root cause at this point wouldn’t make a difference (although I would like to find out eventually).

Here’s what I learned and how I solved the problem.

When you have not provided an explicit excerpt, WordPress creates one via wp_trim_excerpt2:

<br />
function wp_trim_excerpt($text = '') {<br />
	$raw_excerpt = $text;<br />
	if ( '' == $text ) {<br />
	$text = get_the_content('');</p>
<p>	$text = strip_shortcodes( $text );</p>
<p>          $text = apply_filters('the_content', $text);<br />
          $text = str_replace(']]&gt;', ']]&amp;gt;', $text);<br />
          $excerpt_length = apply_filters('excerpt_length', 55);<br />
          $excerpt_more = apply_filters('excerpt_more', ' ' . '[...]');<br />
          $text = wp_trim_words( $text, $excerpt_length, $excerpt_more );<br />
      }<br />
      return apply_filters('wp_trim_excerpt', $text, $raw_excerpt);<br />
  }<br />
 

The way wp_trim_excerpt gets called is not super intuitive, but looks like this:

the_excerpt3 calls get_the_excerpt4 via filter hook

wp_trim_excerpt is hooked into get_the_excerpt via a filter hook defined in /wp-includes/default-filters.php5

A quick glance at the code for wp_trim_excerpt reveals that strip_shortcodes is run immediately after getting the content of the post. Also, there is really no way to filter this out easily unless you rewrite the entire function, which is what I did:

</p>
<p>remove_filter( 'get_the_excerpt', 'wp_trim_excerpt'  ); //Remove the filter we don't want<br />
add_filter( 'get_the_excerpt', 'fergcorp_wp_trim_excerpt' ) ); //Add the modified filter<br />
add_filter( 'the_excerpt', 'do_shortcode' ); //Make sure shortcodes get processed</p>
<p>//Copy and paste from /wp-includes/formatting.php:2096<br />
function fergcorp_wp_trim_excerpt($text = '') {<br />
	$raw_excerpt = $text;<br />
	if ( '' == $text ) {<br />
		$text = get_the_content('');<br />
		//$text = strip_shortcodes( $text ); //Comment out the part we don't want<br />
		$text = apply_filters('the_content', $text);<br />
		$text = str_replace(']]&gt;', ']]&amp;gt;', $text);<br />
		$excerpt_length = apply_filters('excerpt_length', 55);<br />
		$excerpt_more = apply_filters('excerpt_more', ' ' . '[...]');<br />
		$text = wp_trim_words( $text, $excerpt_length, $excerpt_more );<br />
	}<br />
	return apply_filters('wp_trim_excerpt', $text, $raw_excerpt);<br />
}<br />

So there’s the solution. It’s definitely a bit longer than the way it used to work, but it should do just fine.


  1. functions.php or elsewhere 

  2. WordPress 3.4.1: /wp-includes/formatting.php -> line 2096 

  3. WordPress 3.4.1: /wp-includes/post-template.php -> line 240 

  4. WordPress 3.4.1: /wp-includes/post-template.php -> line 258 

  5. WordPress 3.4.1, line 147: add_filter( ‘get_the_excerpt’, ‘wp_trim_excerpt’ );