Changing WordPress Excerpt Length and the Read More Text
Both filters only touch excerpts WordPress generates. A manual excerpt is printed as written, and the Post Excerpt block sets its own length at a priority nothing in functions.php can beat.
Both filters only act on excerpts WordPress has to invent. excerpt_length sets how many words wp_trim_excerpt() keeps when a post has no excerpt of its own; excerpt_more sets the string it appends. If the post has a manual excerpt, neither runs, and the text is printed exactly as the author left it. Most "the filter does nothing" reports are a manual excerpt, a theme hooking at a later priority, or the Post Excerpt block overriding both.
The two filters
add_filter( 'excerpt_length', 'ti_excerpt_length', 999 );
function ti_excerpt_length( $length ) {
return 30;
}
add_filter( 'excerpt_more', 'ti_excerpt_more', 999 );
function ti_excerpt_more( $more ) {
return ' … <a class="ti-more" href="' . esc_url( get_permalink() ) . '">'
. esc_html__( 'Continue reading', 'ti' ) . '</a>';
}
The default length is 55 words and the default suffix is […]. The priority of 999 is not superstition: many themes set their own length at priority 999 or similar, and the last callback wins. If a theme uses 999 and yours is 10, the theme wins and the filter looks ignored. Go higher. get_permalink() works inside the excerpt_more callback because the filter runs inside the loop for the current post.
What the filters never touch
| Source of the excerpt | Filters apply? |
|---|---|
| Auto-generated from content, classic theme loop | Yes |
| Manual excerpt entered in the editor | No — printed as is, no suffix |
| Post Excerpt block | Length is the smaller of your filter and the block's own setting; suffix text survives but its HTML is stripped |
| RSS feed | Both apply, and the suffix HTML is emitted into the feed as HTML — guard the callback with is_feed() |
REST API excerpt.rendered |
Yes, through get_the_excerpt |
wp_trim_words() called by a plugin |
No — it is a different function with its own arguments |
The manual-excerpt row explains the inconsistency people see on archive pages: posts written by an author who fills the Excerpt panel appear at full length and with no link, while the rest are trimmed. The fix is editorial, not code. Either every post gets a manual excerpt, or none does.
The Post Excerpt block
In a block theme the archive is built from the Query Loop and the Post Excerpt block, and that block has a "Max number of words" control that defaults to 55 and cannot be emptied. On the front end the block first calls get_the_excerpt() — your two filters run normally — and then trims the result again to its own word count with wp_trim_words(), which also strips every tag. The visible length is therefore whichever is smaller, your filter or the block setting, and a link returned by excerpt_more arrives as plain text. The block renders its own "read more" link from its settings, so on a block theme that control is where the link lives, and the suffix filter is best left returning plain text. If a theme is rendering excerpts in unexpected ways, the query loop block settings are the first place to look.
Words, not characters
excerpt_length counts words, and wp_trim_words() splits on whitespace. A length of 30 gives thirty words, which is one and a half lines of a wide layout or five lines on a phone. A character-based limit requires replacing the excerpt entirely through get_the_excerpt or wp_trim_excerpt:
add_filter( 'wp_trim_excerpt', 'ti_excerpt_by_chars', 10, 2 );
function ti_excerpt_by_chars( $text, $raw_excerpt ) {
if ( '' !== $raw_excerpt ) {
return $text;
}
return mb_strimwidth( wp_strip_all_tags( $text ), 0, 160, '…' );
}
The $raw_excerpt check keeps manual excerpts intact, matching the behaviour of the built-in filters. This filter runs after the word trim and after the suffix has been appended, and wp_strip_all_tags() removes the suffix link, so a character limit and an HTML "read more" do not combine — print the link in the template instead.
HTML in excerpts
Automatic excerpts are stripped of tags before trimming, and the excerpt_more string is appended afterwards, which is why a link in the suffix survives and a link in the content does not. If the suffix is printed with its tags visible, something downstream is escaping the excerpt — commonly a theme that runs esc_html() on it, or a block pattern that prints it as plain text. That is a theme fix, and the theme.json or template part involved is where it lives, not in the filter.
Verify
Create two test posts: one with a manual excerpt and one without. On an archive page the second should show your word count and your suffix, and the first should show the manual text unchanged. If the second also shows the default […], a later callback is overriding yours — list the callbacks with a quick print_r( $GLOBALS['wp_filter']['excerpt_more'] ) from a snippet to see the priorities in play. On a block theme, open the Post Excerpt block in the Site Editor and set "Max number of words" to match your filter before testing again, or the test is measuring whichever of the two is shorter.
Frequently asked
- Those posts have a manual excerpt in the Excerpt panel. Both filters run inside wp_trim_excerpt, which only runs when the excerpt field is empty. A manual excerpt is printed in full, with no trailing text.
- The block trims the excerpt a second time to its own Max number of words setting, which defaults to 55 and cannot be cleared, and strips tags while doing it. Your filter still runs first, so the shorter of the two lengths wins. Set the length in the block's sidebar and keep the two in agreement.
- The default value is ' […]' and your callback replaces it, so brackets mean a theme or plugin callback at a later priority is adding them again. Register yours at 999 or higher to run last.