Signature
strpos(string $haystack, string $needle, int $offset = 0): int|falsePurpose
strpos() looks for the first occurrence of one string (the needle) inside another string (the haystack). If it finds the needle, it returns the zero-based position where the match starts. If it does not, it returns false. It is the standard tool when you need to know where something is, for example to split a string at a separator or to scan for repeated matches.
If you only need a yes/no answer, str_contains() is usually clearer. If you need to ignore case, use stripos().
Signature
Parameters and defaults
$haystack: the string to search in.$needle: the string to look for. The search is case-sensitive. Since PHP 8.0 an empty string is accepted, and passing an integer as the needle is no longer supported, so cast or convert explicitly if you hold a non-string value.$offset: where the search begins. The default is0, the start of the haystack. A positive value skips that many bytes from the start. A negative value starts that many bytes back from the end of the haystack (supported since PHP 7.1).
Return value and failure cases
The function returns an int position counted from the beginning of the haystack, regardless of the offset you passed. Positions start at 0, not 1. When the needle is not found, the result is false.
If $offset is greater than the length of the haystack, PHP throws a ValueError. An offset exactly equal to the length is allowed; it simply leaves nothing to search.
Because a match at the very start returns 0, which is loosely equal to false, you must test the result with === or !==.
Example 1: strict comparison
The first call matches at position 0. A loose comparison confuses that with failure; a strict one does not.
$haystack = 'abc';
$needle = 'a';
$pos = strpos($haystack, $needle);
var_dump($pos);
var_dump(strpos($haystack, 'z'));
var_dump($pos == false);
var_dump($pos === false);int(0) bool(false) bool(true) bool(false)
Example 2: positive and negative offsets
In 'abcdef abcdef' the letter a sits at positions 0 and 7. The returned number is always relative to the start of the whole string, even when the search starts later.
$s = 'abcdef abcdef';
$results = [
'from start' => strpos($s, 'a'),
'offset 1' => strpos($s, 'a', 1),
'offset -6' => strpos($s, 'a', -6),
'f from -3' => strpos($s, 'f', -3),
'a from -1' => strpos($s, 'a', -1),
];
echo json_encode($results), PHP_EOL;{"from start":0,"offset 1":7,"offset -6":7,"f from -3":12,"a from -1":false}
The offset -6 starts at position 7 in this 13-byte string. The last lookup starts at the final character, f, so no a is found.
Example 3: finding every occurrence
After each match, move the offset past the match and search again. This helper assumes a non-empty needle.
function findAll(string $haystack, string $needle): array
{
$positions = [];
$offset = 0;
while (($pos = strpos($haystack, $needle, $offset)) !== false) {
$positions[] = $pos;
$offset = $pos + strlen($needle);
}
return $positions;
}
echo json_encode(findAll('banana', 'an')), PHP_EOL;
echo json_encode(findAll('aaaa', 'aa')), PHP_EOL;[1,3] [0,2]
The second call shows non-overlapping matches: positions 0 and 2, not 0, 1 and 2. Advance the offset by 1 instead if you want overlapping matches.
Edge cases
- Empty needle: it matches at every position, so
strpos()returns the offset you supplied, or0by default. Never use an empty needle in a loop like the one above, because the offset would never advance. - Offset beyond the end: an offset larger than the haystack length throws
ValueError. Checkstrlen()first, or catch the exception, when the offset comes from user input. - Bytes, not characters: the function is binary-safe and counts bytes. In UTF-8 text with multibyte characters, the position is a byte index. Use
mb_strpos()when you need a character index. - Case sensitivity:
strpos('Hello', 'h')isfalse. Usestripos()for case-insensitive search.
Common mistakes
- Using
if (strpos(...))or== false. A match at position 0 is treated as a failure. Always write=== falseor!== false. - Assuming the result is relative to the offset. The result is measured from the start of the haystack, so do not add the offset to it again.
- Swapping the arguments. The haystack comes first and the needle second; reversing them silently searches for the wrong thing.
- Expecting character positions in multibyte text. Byte positions can differ from visible character counts.
- Using
strpos()for a simple containment check.str_contains()expresses the intent better and avoids the0versusfalsetrap.
Related functions
stripos(): case-insensitive first occurrence.strrpos()andstrripos(): last occurrence.str_contains(),str_starts_with(),str_ends_with(): boolean checks.strstr(): returns the part of the string from the first match.strpbrk(): searches for any of a set of characters.substr(): extracts text once you know a position.preg_match(): pattern-based matching.
Recap
strpos() returns the zero-based byte position of the first case-sensitive match, or false when there is none. Compare with ===, remember that the result is independent of the offset, and keep the offset within the haystack length to avoid a ValueError.
Tested examples
$haystack = 'abc';
$needle = 'a';
$pos = strpos($haystack, $needle);
var_dump($pos);
var_dump(strpos($haystack, 'z'));
var_dump($pos == false);
var_dump($pos === false);
int(0) bool(false) bool(true) bool(false)
$s = 'abcdef abcdef';
$results = [
'from start' => strpos($s, 'a'),
'offset 1' => strpos($s, 'a', 1),
'offset -6' => strpos($s, 'a', -6),
'f from -3' => strpos($s, 'f', -3),
'a from -1' => strpos($s, 'a', -1),
];
echo json_encode($results), PHP_EOL;
{"from start":0,"offset 1":7,"offset -6":7,"f from -3":12,"a from -1":false}
function findAll(string $haystack, string $needle): array
{
$positions = [];
$offset = 0;
while (($pos = strpos($haystack, $needle, $offset)) !== false) {
$positions[] = $pos;
$offset = $pos + strlen($needle);
}
return $positions;
}
echo json_encode(findAll('banana', 'an')), PHP_EOL;
echo json_encode(findAll('aaaa', 'aa')), PHP_EOL;
[1,3] [0,2]