$indexOfBytes is a MongoDB aggregation expression that returns the zero-based UTF-8 byte index of the first match of a substring inside a string. Not found → -1. For plain English (ASCII) text, byte index equals character index.
Remember
$indexOfBytes ≈ string indexOf, counted in UTF-8 bytes
{ $indexOfBytes: [ <string>, <substring>, <start>, <end> ] }
found → first byte index (0, 1, 2, …)
not found → -1
null / missing string → null
non-string (not null) → pipeline error
Pass [ "$field", "needle" ] to get the first byte index.
Optionally add start (and end) as non-negative byte offsets.
Treat -1 as “not found”; treat null as “no string”.
Piece
Required?
Role
String
Yes
Expression that resolves to a string
Substring
Yes
Text to locate (must be a string)
Start / end
No
Search from byte start up to (not including) end
Pseudocode
Pseudocode
for each document:
s = evaluate(stringExpression)
needle = evaluate(substringExpression)
if s is null or missing:
result = null
else if s is not a string:
error
else if needle is null or not a string:
error
else:
from = start or 0 // byte offset
to = end or byteLength(s)
result = first UTF-8 byte index of needle in s[from..to)
if not found: result = -1
emit { ..., field: result }
Reference
Cheat Sheet
Keep these facts handy while writing string-search expressions.
Text "foo.bar.baz". First "." is at byte 3; searching from byte 4 finds the second "." at 7.
Calculation
text = "foo.bar.baz"
bytes: 0123456789A (each letter = 1 byte in ASCII)
$indexOfBytes(text, ".")
→ first "." at byte 3 → 3
$indexOfBytes(text, ".", 4)
→ skip "foo." → next "." at byte 7 → 7
$indexOfBytes(text, "zzz")
→ no hit → -1
Tip: for accents/emoji, use $indexOfCP if you want character positions.
Code
MongoDB Examples
We use a small messages collection with a text field. First we find a substring’s byte index, then we search from a start byte. Insert the sample data once in mongosh.
Example 1 — Sample Data
Imagine three short messages. Each has an id and a text string. We save them in a collection named messages.
"hello world" (ASCII = 1 byte per character)
index: 0123456789A
chars: h e l l o w o r l d
0 1 2 3 4 5 6 7 8 9 10
"world" starts at byte 6
"xyz" → not here (-1)
How It Works
Insert once.insertMany adds three message documents.
MongoDB stores strings as written. Spaces and dots count as characters (and bytes in ASCII).
find() shows the same shape. What you inserted is what you get back—ready for $indexOfBytes next.
Example 2 — Find First Index
Goal: for each message, find where "o" first appears (byte index in ASCII text).
Same stored documents. Still using the three messages from Example 1.
Start at byte 4. MongoDB ignores bytes 0–3 for this search.
foo.bar.baz. First "." is at 3 (skipped); next "." is at 7.
Others stay -1. hello and code have no "." after the start byte.
Edge Cases & Pitfalls
Check these before using $indexOfBytes in production pipelines.
utf-8
Bytes ≠ characters
Accented letters and emoji often use more than one UTF-8 byte. Prefer $indexOfCP when you want character positions.
-1
-1 means not found
Do not treat -1 as a valid cut point for $substrBytes. Branch with $cond first.
null
Null string vs null substring
A null/missing string returns null. A null substring errors. Keep the needle a real string.
range
start / end must be non-negative
Negative offsets error. Start past the end of the string (or past end) returns -1.
Analysis
Performance Notes
Approach
Cost
Best for
$indexOfBytes
O(string length) per doc
Short strings; need a byte offset
+ start / end
Smaller scan
Skip a known prefix / window
$indexOfCP
Similar order
Human character positions
Searching long text on every document adds up. Prefer $match first when only some rows need a lookup, and keep strings reasonably short when you call $indexOfBytes often.
Remember
Key Takeaways
Role: first UTF-8 byte index of a substring.
Not found: returns -1 (not an error).
Range: optional start / end are byte offsets.
Characters? use $indexOfCP instead of bytes.
One line:$indexOfBytes answers “at which UTF-8 byte does this substring start?”—or -1 if missing.
Frequently Asked Questions
$indexOfBytes returns the zero-based UTF-8 byte index of the first occurrence of a substring inside a string. Use it inside aggregation expression stages such as $project and $addFields.
Use { $indexOfBytes: [ <string>, <substring>, <start>, <end> ] }. start and end are optional byte offsets that limit the search range.
It returns -1 when the substring is not present in the string (within the optional start/end byte range).
$indexOfBytes counts UTF-8 bytes. $indexOfCP counts Unicode code points (characters). For ASCII-only text they match; for emoji or accented letters the indexes can differ.
If the string expression is null or refers to a missing field, $indexOfBytes returns null. A non-string (and not null) value causes an error. A null substring also errors.
Pass optional start (and optionally end) as non-negative byte offsets. Searching begins at start and stops before end.