Str::mask
Replaces a portion of a string with a repeated character to redact card numbers, emails or phone numbers.
Str::mask(string $string, string $character, int $index, ?int $length = null, string $encoding = 'UTF-8'): stringParameters
| Parameter | Type | Description |
|---|---|---|
$$string | string | The string to mask. |
$$character | string | The mask character. Only its first character is used; an empty string returns the input unchanged. |
$$index | int | Character offset where masking starts. A negative value counts back from the end of the string. |
$$lengthoptional | int|null= null | How many characters to mask. Null masks to the end; a negative value stops that many characters before the end. |
$$encodingoptional | string= 'UTF-8' | Encoding used to split the string into characters. |
Return value
stringThe string with the selected segment replaced by repeats of $character, preserving the original length.
Str::mask replaces a slice of a string with a repeated character and leaves the rest visible. It is the helper for showing a payment method, a partially hidden email, or a phone number in a support UI without exposing the whole value.
Selecting the slice
The offset and length behave exactly like mb_substr, and this is where the method earns its keep. A negative $index counts back from the end, which is how you mask a fixed number of characters near the tail. A negative $length means "stop this many characters short of the end", which is the idiomatic way to redact a card: start at 0, length -4, and the last four digits survive no matter how long the number is.
If the selection is empty — an index past the end of the string, or a length that resolves to zero — the input is returned untouched rather than raising an error. That silent no-op is worth guarding against when the offsets are computed from user data.
Length is preserved
Exactly one mask character replaces each masked character, so the output is the same length as the input and reveals how long the secret was. If that matters, build a fixed-width placeholder yourself instead.
Example
When to use it
- Show a stored card as ************4242 in a billing settings screen.
- Redact the local part of an email address in an admin activity log.
- Partially hide a phone number or national ID in a support agent's view.
Frequently asked questions
How do I show only the last 4 digits of a credit card in Laravel?
Call Str::mask($number, '*', 0, -4). The negative length stops masking four characters before the end, so it works for 15-digit and 16-digit numbers alike without any arithmetic on your side.
Why did Str::mask return my string unchanged?
The selected segment came out empty — usually an $index beyond the end of the string, or a negative $length longer than the string itself. The method returns the input rather than throwing, so check your offsets against Str::length first.
Can I mask with more than one character, like '**'?
No. Only the first character of $character is repeated, so '**' and '*' produce identical output. The result always has the same length as the input, one mask character per hidden character.
More examples
Masking a card regardless of its length
A negative length is measured from the end, so the same call keeps exactly four digits visible on both a 16-digit and a 15-digit number. Hard-coding a positive length would have leaked a fifth digit on the shorter card.
<?php
use Illuminate\Support\Str;
$cards = [
'4242424242424242', // Visa, 16 digits
'378282246310005', // Amex, 15 digits
];
foreach ($cards as $card) {
echo Str::mask($card, '*', 0, -4), "\n";
}
// ************4242
// ***********0005Redacting an email without hiding the domain
The domain stays readable so a support agent can confirm which provider the account uses, while the local part is hidden. The guard matters: without it, a value with no @ would fall through and be echoed in full.
<?php
use Illuminate\Support\Str;
function redactEmail(string $email): string
{
$at = mb_strpos($email, '@');
if ($at === false || $at < 2) {
return Str::mask($email, '*', 0);
}
// Keep the first character, mask the rest of the local part.
return Str::mask($email, '*', 1, $at - 1);
}
echo redactEmail('[email protected]'), "\n";
// t*****@example.com
echo redactEmail('[email protected]'), "\n";
// j*@example.com
echo redactEmail('not-an-email'), "\n";
// ************Redacting in a log context
Masking at the point the context array is built means the raw value never reaches the log driver, so it cannot leak into a file, a third-party log service, or an exception report. Requires a booted application for the Log facade.
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class LogPaymentAttempt
{
public function handle(object $event): void
{
Log::info('Payment attempted', [
'user_id' => $event->userId,
'card' => Str::mask($event->cardNumber, '*', 0, -4),
'phone' => Str::mask($event->phone, '*', 3, -2),
]);
}
}
Discussion