Str::limit
Truncates a string to a maximum display width and appends an ellipsis, optionally without splitting a word.
Str::limit(string $value, int $limit = 100, string $end = '...', bool $preserveWords = false): stringParameters
| Parameter | Type | Description |
|---|---|---|
$$value | string | The string to truncate. |
$$limitoptional | int= 100 | Maximum display width to keep, measured with mb_strwidth. The width of $end is not counted against it. |
$$endoptional | string= '...' | String appended after truncation. Pass an empty string to append nothing. |
$$preserveWordsoptional | bool= false | When true, the cut is pulled back to the last whitespace so no word is split; the input is also run through strip_tags and has newlines collapsed to spaces first. |
Return value
stringThe original string when it already fits, otherwise the truncated string with $end appended.
Changelog
- PHP 11.xThe preserveWords argument was added. In 10.x and earlier, limit always cut at the exact width, mid-word.
Str::limit is the excerpt helper: shorten a description for a card, a subject line for a list, a log message for a column. If the value already fits, it is returned untouched and no ellipsis is added — so you never get Short....
It measures width, not characters
The check and the cut both use mb_strwidth / mb_strimwidth, which count East Asian wide characters as two columns. Ten Japanese characters have a width of 20, so Str::limit($japanese, 8) keeps four of them, not eight. For Latin text width and character count coincide, which is why this rarely surfaces until someone pastes CJK text.
The appended string is extra
$end is concatenated after truncation, so the result can be longer than $limit. Budget for it yourself if you are filling a fixed-width column: pass $limit - 3.
preserveWords changes more than the cut point
Setting it to true also strips HTML tags and collapses newlines before truncating — a quiet difference from the default branch, which leaves markup alone and can slice a tag in half. If you are truncating HTML for display, you want preserveWords: true, or you want to strip the tags yourself first.
Example
When to use it
- Build a card excerpt from the first 120 characters of a post body.
- Shorten a long error message before writing it to a fixed-width log column.
- Trim a filename or subject line for a table cell without breaking a word.
Frequently asked questions
How do I truncate a string in Laravel without cutting a word in half?
Pass preserveWords: true, which pulls the cut back to the last whitespace before the limit. It was added in Laravel 11; on older versions you have to truncate and then trim back to the final space yourself.
Why is my truncated string longer than the limit I set?
Because $end is appended after truncation rather than counted against $limit, so a 20-character limit with the default '...' yields 23 characters. Pass $limit - strlen($end) when you need a hard maximum.
Why does Str::limit cut Japanese or Chinese text shorter than expected?
It measures display width with mb_strwidth, and CJK characters are two columns wide, so a limit of 8 keeps only four of them. Use mb_substr directly if you need an exact character count.
More examples
Truncating HTML, with and without preserveWords
The default branch treats the markup as ordinary text and cuts inside the <strong> tag, producing broken HTML. preserveWords runs strip_tags first, so it measures and truncates the visible text only. This is the usual reason an excerpt renders with a stray angle bracket.
<?php
use Illuminate\Support\Str;
$html = '<p>The <strong>quick</strong> brown fox</p>';
echo Str::limit($html, 12), "\n";
// <p>The <stro...
echo Str::limit($html, 12, preserveWords: true), "\n";
// The quick...Excerpts in a Blade view
Shows the three shapes you actually write in an application: a custom single-character ellipsis for headlines, word-preserving truncation for body copy, and the Stringable chain where squish collapses the whitespace left behind by stripped markup. Needs a booted application and Eloquent models.
<?php
// resources/views/posts/index.blade.php
?>
@foreach ($posts as $post)
<article>
<h2>{{ Str::limit($post->title, 60, '…') }}</h2>
<p>{{ Str::limit(strip_tags($post->body), 160, preserveWords: true) }}</p>
{{-- The fluent form reads better in a chain --}}
<small>{{ str($post->body)->stripTags()->squish()->limit(80) }}</small>
</article>
@endforeachFitting a fixed-width column
Passing $width directly would have produced a 23-character cell and broken the table, because the '...' is added after the limit is applied rather than counted inside it.
<?php
use Illuminate\Support\Str;
$width = 20;
$rows = [
'orders.created',
'notifications.channel.slack.dispatch.failed',
];
foreach ($rows as $row) {
// Subtract the ellipsis, because $end is appended on top of $limit.
$cell = Str::limit($row, $width - 3);
echo '|', str_pad($cell, $width), "|\n";
}
// |orders.created |
// |notifications.cha...|
Discussion