Signature
str_ends_with(string $haystack, string $needle): boolPurpose
str_ends_with() answers a simple yes-or-no question: does one string finish with another string? It is useful for checking file extensions, URL suffixes, identifiers such as _id, or whether a path already has a trailing slash. Before it existed, developers often combined substr(), strlen() or strrpos() to get the same answer. The dedicated function is shorter and states the intent directly.
The comparison is case-sensitive and binary-safe. That means it compares raw bytes and is not confused by null bytes inside a string.
Signature
Parameters and defaults
$haystackis the string to inspect. It has no default.$needleis the suffix you expect to find at the very end of the haystack. It has no default.
Both parameters are required. You can pass them by name, for example str_ends_with(haystack: $name, needle: '.csv'), but positional arguments are the usual style.
Return value and failure cases
The function returns true when $haystack ends with $needle, and false otherwise. It never returns a position, a count or null, so you can use the result directly in a condition.
There is no "not found" error value to confuse with a valid result, unlike strpos(), which can return 0 or false. Passing a value that cannot be used as a string, such as an array, throws a TypeError. With declare(strict_types=1), passing an integer or other non-string scalar also throws a TypeError.
Example: basic suffix checks
The first example checks a file name against three suffixes. Note that the last check fails because the letter case differs.
$file = 'report-2024.csv';
echo json_encode([
'csv' => str_ends_with($file, '.csv'),
'pdf' => str_ends_with($file, '.pdf'),
'case' => str_ends_with($file, '.CSV'),
]), "\n";{"csv":true,"pdf":false,"case":false}
Example: edge cases
The empty string is the most surprising input. Every string ends with the empty string, including another empty string. A needle longer than the haystack can never match, and a needle equal to the haystack always does.
$results = [
'empty needle' => str_ends_with('abc', ''),
'both empty' => str_ends_with('', ''),
'empty haystack' => str_ends_with('', 'a'),
'needle longer' => str_ends_with('abc', 'zabc'),
'whole string' => str_ends_with('abc', 'abc'),
];
echo json_encode($results), "\n";{"empty needle":true,"both empty":true,"empty haystack":false,"needle longer":false,"whole string":true}
Example: small helper functions
Because the function is case-sensitive, wrap it when you need case-insensitive behavior. For ASCII text, lowercase both strings first. The second helper below normalizes a path so that it always ends with a slash.
function withTrailingSlash(string $path): string
{
return str_ends_with($path, '/') ? $path : $path . '/';
}
function hasExtension(string $name, string $extension): bool
{
return str_ends_with(strtolower($name), strtolower($extension));
}
echo json_encode([
withTrailingSlash('/var/www'),
withTrailingSlash('/var/www/'),
hasExtension('PHOTO.JPG', '.jpg'),
hasExtension('photo.png', '.jpg'),
], JSON_UNESCAPED_SLASHES), "\n";["/var/www/","/var/www/",true,false]
Edge cases to remember
- Empty needle:
str_ends_with($anything, '')is alwaystrue. If the needle comes from user input or configuration, check for an empty value first when that would be a mistake in your program. - Exact match: a haystack identical to the needle counts as ending with it.
- Whitespace and newlines: a string such as
"line\n"ends with"\n", but it does not end with"line". Trim input first if trailing whitespace should be ignored. - Bytes, not characters: the comparison works on bytes. For ordinary UTF-8 text with a valid UTF-8 needle this behaves as you would expect, but the function does not apply any Unicode-aware case folding or normalization.
- Binary data: because the function is binary-safe, strings containing null bytes are compared correctly.
Common mistakes
- Expecting case-insensitive matching.
'Fence'and'fence'are different needles. Normalize both strings yourself, or use a different approach when you need more complex rules. - Swapping the arguments. The haystack comes first and the suffix second. Writing
str_ends_with('.csv', $file)asks a different question and usually returnsfalse. - Forgetting the empty string rule. A blank extension or suffix makes every check succeed.
- Using it for patterns. The needle is a literal string, not a wildcard or regular expression. For patterns, use
preg_match(). - Checking only the suffix for security. A name ending in
.jpgis not proof that the content is an image. Treat suffix checks as a convenience, not validation of the data. - Passing arrays or other unsupported values. This causes a
TypeError, so convert or validate values before calling the function.
Related functions
str_starts_with()checks the beginning of a string instead of the end.str_contains()checks whether the needle appears anywhere.substr()extracts part of a string, which is useful when you also need the suffix itself.strrpos()andstrripos()find the last occurrence of a substring, withstrripos()ignoring case.stripos(),strstr()andstrpbrk()cover other search tasks.preg_match()handles pattern-based checks, such as an end-of-string anchor with$.
Recap
Use str_ends_with($haystack, $needle) for a clear, case-sensitive suffix test that returns a boolean. Remember that an empty needle always matches, that a longer needle never matches, and that you must normalize case yourself when needed. The function is binary-safe and the needle is always treated literally.
Tested examples
$file = 'report-2024.csv';
echo json_encode([
'csv' => str_ends_with($file, '.csv'),
'pdf' => str_ends_with($file, '.pdf'),
'case' => str_ends_with($file, '.CSV'),
]), "\n";
{"csv":true,"pdf":false,"case":false}
$results = [
'empty needle' => str_ends_with('abc', ''),
'both empty' => str_ends_with('', ''),
'empty haystack' => str_ends_with('', 'a'),
'needle longer' => str_ends_with('abc', 'zabc'),
'whole string' => str_ends_with('abc', 'abc'),
];
echo json_encode($results), "\n";
{"empty needle":true,"both empty":true,"empty haystack":false,"needle longer":false,"whole string":true}
function withTrailingSlash(string $path): string
{
return str_ends_with($path, '/') ? $path : $path . '/';
}
function hasExtension(string $name, string $extension): bool
{
return str_ends_with(strtolower($name), strtolower($extension));
}
echo json_encode([
withTrailingSlash('/var/www'),
withTrailingSlash('/var/www/'),
hasExtension('PHOTO.JPG', '.jpg'),
hasExtension('photo.png', '.jpg'),
], JSON_UNESCAPED_SLASHES), "\n";
["/var/www/","/var/www/",true,false]