WordPress limits post excerpts by word count by default — 55 words — but word-based truncation creates uneven output lengths that break grid layouts, card components, and archive pages where consistent excerpt height matters. A post with long words hits the character limit visually before a post with short words, and the result is a layout that looks broken without any code being wrong.
The solution is character-based truncation. WordPress does not provide a built-in parameter for this — get_the_excerpt() accepts only a post ID or post object, not a length. Character limiting requires wrapping the excerpt output in a PHP function or filter. This guide covers five approaches, from the simplest one-liner to a robust reusable function suitable for production themes and plugins, with copy-ready code for each.
Understanding How get_the_excerpt() Works
Before applying character limits, it helps to understand what get_the_excerpt() actually returns. The function checks whether the current post has a manually written excerpt in the WordPress editor. If it does, it returns that exact text. If it does not, it generates an automatic excerpt by taking the post content, stripping shortcodes and HTML tags, and trimming it to the excerpt length — 55 words by default, or whatever the excerpt_length filter sets.
The companion function the_excerpt() wraps the return value of get_the_excerpt() in a <p> tag and echoes it directly. When you need to manipulate the string — limit characters, strip tags, append custom read-more links — you always work with get_the_excerpt() to get the raw string, then handle output yourself.
One important behavior to account for: if the post has a manually written excerpt and that excerpt is shorter than your character limit, all five methods below return it intact without appending an ellipsis. This is correct behavior — you only truncate when the text exceeds the limit. The distinction between functions that return a value versus functions that echo directly is the foundational concept that makes all of the approaches below work correctly in template files.
Method 1: mb_strimwidth() — The Simplest One-Liner
The fastest approach for inline use in a template file is wrapping get_the_excerpt() directly in PHP’s mb_strimwidth() function. This is a multibyte-safe string width function that truncates to an exact character count and appends a suffix automatically.
<?php
// Limit excerpt to 150 characters with ellipsis
echo mb_strimwidth( get_the_excerpt(), 0, 150, '...' );
?>
The four parameters are: the input string, the starting position (always 0), the character limit, and the string to append when truncation occurs. The function is UTF-8 safe, which means it handles multibyte characters — Arabic, Chinese, Japanese, emoji — correctly without corrupting the string or miscounting characters. Standard PHP string functions like substr() count bytes rather than characters and will produce broken output on non-ASCII text.
The limitation of this method is that it cuts at an exact character boundary, which may fall in the middle of a word. “The quick brown fo…” is valid output from this function. If cutting at word boundaries matters for readability, Method 3 handles this cleanly.
Method 2: wp_trim_words() — Word-Based with Controlled Output
If consistent word count is more important than consistent character count in your specific layout, WordPress’s native wp_trim_words() function provides cleaner output than mb_strimwidth() because it never cuts a word in half. It accepts the string, a word count, and a custom more string.
<?php
// Limit to 25 words with custom ellipsis
echo wp_trim_words( get_the_excerpt(), 25, '...' );
?>
This is the approach WordPress itself uses internally. The output always ends on a complete word boundary. The trade-off is that 25 short words and 25 long words produce visibly different text lengths, which matters in card grid layouts where visual consistency is the goal. For single-column layouts or anywhere excerpt length consistency is less critical, this is the simpler and more readable approach.
You can also use wp_trim_words() on get_the_content() for pages that do not use the excerpt field, giving you a programmatically generated summary from full post content without any dependency on the excerpt being filled in.
<?php
// Generate a 25-word summary from post content when no excerpt is set
echo wp_trim_words( get_the_content(), 25, '...' );
?>
Method 3: Custom Function with Word-Boundary Truncation
This is the most robust approach for production use — it enforces a character limit while cutting at the nearest word boundary, so the output never ends mid-word. Add this to your theme’s functions.php or a site-specific plugin file.
/**
* Returns excerpt limited to a character count, cut at nearest word boundary.
*
* @param int $char_limit Maximum number of characters.
* @param int|null $post_id Optional. Post ID. Defaults to current post in loop.
* @return string Truncated excerpt with ellipsis if trimmed.
*/
function get_excerpt_char_limit( $char_limit = 150, $post_id = null ) {
$excerpt = get_the_excerpt( $post_id );
// Strip any residual HTML tags for clean character counting
$excerpt = wp_strip_all_tags( $excerpt );
// Return as-is if already within limit
if ( mb_strlen( $excerpt ) <= $char_limit ) {
return $excerpt;
}
// Truncate to character limit
$excerpt = mb_substr( $excerpt, 0, $char_limit );
// Cut back to nearest word boundary to avoid mid-word truncation
$excerpt = preg_replace( '/\s+?(\S+)?$/u', '', $excerpt );
return $excerpt . '…';
}
Usage inside The Loop in any template file:
<?php echo get_excerpt_char_limit( 160 ); ?>
Usage outside The Loop, passing a specific post ID:
<?php echo get_excerpt_char_limit( 160, get_the_ID() ); ?>
The preg_replace with the /u flag is Unicode-safe and trims back from the truncation point to the nearest whitespace boundary, ensuring the output always ends on a complete word. The … HTML entity renders as a proper typographic ellipsis (…) rather than three dots.
Method 4: Global Filter via functions.php
If you want every excerpt on the entire site to use a consistent character limit without modifying individual template files, apply the limit through the get_the_excerpt filter hook. This approach intercepts every excerpt retrieval site-wide.
/**
* Apply a global character limit to all excerpts.
* Add to functions.php or a site-specific plugin.
*/
add_filter( 'get_the_excerpt', 'global_excerpt_char_limit', 20 );
function global_excerpt_char_limit( $excerpt ) {
// Don't truncate in admin context
if ( is_admin() ) {
return $excerpt;
}
$char_limit = 155;
$excerpt = wp_strip_all_tags( $excerpt );
if ( mb_strlen( $excerpt ) <= $char_limit ) {
return $excerpt;
}
$excerpt = mb_substr( $excerpt, 0, $char_limit );
$excerpt = preg_replace( '/\s+?(\S+)?$/u', '', $excerpt );
return $excerpt . '…';
}
Two important notes on this approach. First, the is_admin() check prevents the filter from truncating excerpts in the WordPress dashboard — post list tables show excerpt previews and you do not want those chopped. Second, the priority parameter 20 in add_filter ensures this filter runs after WordPress’s own excerpt filters, which use priority 10. Running at a lower priority number would truncate before WordPress finishes processing the excerpt, potentially leaving unclosed HTML or shortcode artifacts in the output.
This global approach works well for themes where excerpt display is consistent across all post types. If different post types need different limits — for example, 120 characters for blog posts and 200 for product descriptions — Method 3’s per-call parameter approach is more flexible.
Method 5: Limit the_content() by Characters as a Fallback
Some sites do not use the excerpt field at all and generate summaries directly from post content. This approach pulls from get_the_content(), strips all formatting, and applies the same character-limit logic. It is useful as a fallback when the excerpt field is empty, or as the primary approach on sites where editors do not fill in manual excerpts.
/**
* Get content-based excerpt with character limit.
* Falls back to post content if excerpt field is empty.
*
* @param int $char_limit Maximum number of characters.
* @return string Trimmed excerpt string.
*/
function get_content_excerpt( $char_limit = 155 ) {
global $post;
// Use manual excerpt if available
if ( ! empty( $post->post_excerpt ) ) {
$text = $post->post_excerpt;
} else {
// Fall back to post content
$text = $post->post_content;
}
// Strip shortcodes and HTML
$text = strip_shortcodes( $text );
$text = wp_strip_all_tags( $text );
// Normalize whitespace
$text = preg_replace( '/\s+/', ' ', trim( $text ) );
if ( mb_strlen( $text ) <= $char_limit ) {
return $text;
}
$text = mb_substr( $text, 0, $char_limit );
$text = preg_replace( '/\s+?(\S+)?$/u', '', $text );
return $text . '…';
}
Usage in template files:
<?php echo get_content_excerpt( 155 ); ?>
The strip_shortcodes() call is important here — without it, a content field containing unclosed shortcodes will produce output that includes raw shortcode markup in the excerpt. The preg_replace( ‘/\s+/’, ‘ ‘, trim( $text ) ) line normalizes multiple consecutive whitespace characters — tabs, newlines, double spaces — that often appear in post content after tag stripping.
Which Method Should You Use?
The right approach depends on where the excerpt appears and how much consistency the layout requires.
For a quick fix in a single template file where you need the excerpt shorter right now, the mb_strimwidth() one-liner in Method 1 is the fastest. Add it inline and move on.
For a production theme where consistent excerpt length across a card grid or archive layout matters, Method 3’s reusable function is the correct choice. Add it to functions.php, call it with different limits in different templates, and the logic is maintainable in one place.
For a site where excerpts display consistently across all templates and post types, the global filter in Method 4 centralizes the logic in functions.php without requiring changes to any template file. This is the right approach for child themes and sites where template files should not contain business logic.
For sites that do not use the excerpt field consistently, Method 5’s content-based fallback ensures every post has a usable excerpt regardless of whether the editor filled in the excerpt field manually.
Avoid using the excerpt_length filter for character limiting — that filter only changes the word count of auto-generated excerpts and has no effect on manually written excerpts or character-based truncation.
Common Issues and How to Fix Them
Ellipsis Not Appearing on Short Excerpts
If manually written excerpts are shorter than the character limit, no ellipsis appears — this is correct behavior. The ellipsis only appends when truncation actually occurs. If every excerpt on the site is well under the limit, the truncation function is working correctly but never triggering.
Excerpt Cutting in the Middle of a Word
This happens when using mb_strimwidth() or mb_substr() without the word-boundary preg_replace step. Switch to Method 3 or Method 5, which both include the preg_replace( ‘/\s+?(\S+)?$/u’, ”, $excerpt ) line that trims back to the nearest complete word.
HTML Tags Appearing in the Excerpt Output
This occurs when wp_strip_all_tags() is not called before truncation, and the excerpt or content contains HTML markup. Add $excerpt = wp_strip_all_tags( $excerpt ); before the character count check in any custom function. Do not use PHP’s native strip_tags() — it does not handle malformed HTML as reliably as WordPress’s wrapper function.
Character Count Is Wrong on Non-English Sites
This happens when using strlen() or substr() instead of their multibyte equivalents. Both native PHP functions count bytes, not characters, and one multibyte character — a Chinese character, an Arabic letter, an emoji — may occupy two to four bytes. Replace all instances with mb_strlen() and mb_substr() and add the following line at the top of functions.php if encoding issues persist:
mb_internal_encoding( 'UTF-8' );
Global Filter Applying in the Admin
If excerpt columns in the WordPress post list table are being truncated unexpectedly, the global filter in Method 4 is running in the admin context. Add the is_admin() guard at the start of the filter callback as shown in the Method 4 code above.
Character Limit Reference by Use Case
The right character limit depends on the display context. These are practical values based on common WordPress layout patterns.
- Three-column card grids: 120–140 characters produces consistent card heights across most font sizes at desktop width.
- Single-column archive pages: 200–250 characters gives enough context without feeling truncated.
- SEO meta descriptions: 145–160 characters matches the Google search snippet display length. This is the most important use case where character-exact limits matter — word-based limits are unreliable here.
- Widget areas and sidebars: 80–100 characters is typically the right range before the excerpt pushes other sidebar content below the fold.
- Mobile-first layouts: Target 100–120 characters — mobile viewports render fewer characters per line and a 200-character excerpt that looks clean on desktop may require scrolling on a narrow screen in a card layout.
For meta description generation specifically, note that search engines count characters including spaces, so a 155-character limit on the excerpt function will produce descriptions that fit within the standard display window without truncation in search results. This makes Method 4’s global filter particularly useful when the same excerpt field is used for both on-page display and programmatic meta description population.
Complete Reference: Quick-Copy Versions of All Five Methods
All five methods in one place for easy reference.
// METHOD 1: One-liner with mb_strimwidth (cuts at exact character, may split word)
echo mb_strimwidth( get_the_excerpt(), 0, 150, '...' );
// METHOD 2: Word-based with wp_trim_words (never splits word, variable char length)
echo wp_trim_words( get_the_excerpt(), 25, '...' );
// METHOD 3: Reusable function (character limit + word boundary safe)
function get_excerpt_char_limit( $char_limit = 150, $post_id = null ) {
$excerpt = wp_strip_all_tags( get_the_excerpt( $post_id ) );
if ( mb_strlen( $excerpt ) <= $char_limit ) {
return $excerpt;
}
$excerpt = mb_substr( $excerpt, 0, $char_limit );
$excerpt = preg_replace( '/\s+?(\S+)?$/u', '', $excerpt );
return $excerpt . '…';
}
echo get_excerpt_char_limit( 160 );
// METHOD 4: Global filter — add to functions.php only
add_filter( 'get_the_excerpt', 'global_excerpt_char_limit', 20 );
function global_excerpt_char_limit( $excerpt ) {
if ( is_admin() ) { return $excerpt; }
$char_limit = 155;
$excerpt = wp_strip_all_tags( $excerpt );
if ( mb_strlen( $excerpt ) <= $char_limit ) { return $excerpt; }
$excerpt = mb_substr( $excerpt, 0, $char_limit );
$excerpt = preg_replace( '/\s+?(\S+)?$/u', '', $excerpt );
return $excerpt . '…';
}
// METHOD 5: Content fallback — uses post content if excerpt field is empty
function get_content_excerpt( $char_limit = 155 ) {
global $post;
$text = ! empty( $post->post_excerpt ) ? $post->post_excerpt : $post->post_content;
$text = preg_replace( '/\s+/', ' ', trim( wp_strip_all_tags( strip_shortcodes( $text ) ) ) );
if ( mb_strlen( $text ) <= $char_limit ) { return $text; }
$text = mb_substr( $text, 0, $char_limit );
$text = preg_replace( '/\s+?(\S+)?$/u', '', $text );
return $text . '…';
}
echo get_content_excerpt( 155 );
Frequently Asked Questions
Does get_the_excerpt() accept a character limit parameter?
No. The get_the_excerpt() function accepts only a post ID or WP_Post object — it has no built-in length parameter of any kind. WordPress’s native excerpt length control is word-based only, via the excerpt_length filter. Character limiting requires wrapping the output in a custom PHP function as shown in the methods above.
What is the difference between get_the_excerpt() and the_excerpt()?
get_the_excerpt() returns the excerpt as a string — it does not output anything. the_excerpt() wraps the result in a <p> tag and echoes it directly to the page. When applying character limits or any string manipulation, always use get_the_excerpt() to get the raw string, apply the manipulation, then echo the result yourself. Using the_excerpt() with string functions will not work because it returns void, not a string.
Why use mb_strlen() instead of strlen() for character counting?
strlen() counts bytes, not characters. ASCII characters use one byte each, so it works correctly for English text. Multibyte characters — used in Arabic, Chinese, Japanese, Korean, emoji, and many European character sets — use two to four bytes each. Counting them with strlen() produces incorrect counts and truncating with substr() can corrupt multibyte sequences, producing broken characters in the output. Always use mb_strlen() and mb_substr() for any string that may contain non-ASCII content.
Will changing the excerpt_length filter limit characters instead of words?
No. The excerpt_length filter only controls the word count of automatically generated excerpts. It has no effect on manually written excerpts and cannot be used to set a character limit. Character limiting requires the custom function approach shown in Methods 3, 4, and 5 above.
How do I set different character limits for different post types?
Use the reusable function from Method 3 and call it with different limits in each post type’s template file. The $char_limit parameter accepts any integer, so get_excerpt_char_limit( 120 ) in your blog archive template and get_excerpt_char_limit( 200 ) in your portfolio archive template apply independent limits from the same shared function without any additional code.
Can I use these methods outside The Loop?
Yes, by passing a post ID as the second parameter to get_excerpt_char_limit() as shown in Method 3. When called outside The Loop — in a widget, a shortcode, or a custom query — WordPress needs the post ID explicitly because there is no global post object to fall back on. Pass get_the_ID() or a specific integer post ID as the second argument.
Which method is safest to use on a multilingual site?
Methods 3, 4, and 5 are all safe for multilingual sites because they use mb_strlen(), mb_substr(), and the /u Unicode flag on the preg_replace call throughout. Method 1 using mb_strimwidth() is also safe. The only unsafe approach for multilingual content is using native strlen() and substr() directly, which Methods 1 through 5 all avoid.