$indexOfCP is a MongoDB aggregation expression that returns the zero-based Unicode code point index of the first match of a substring inside a string. Not found → -1. Think “character position,” not UTF-8 bytes.
Remember
$indexOfCP ≈ string indexOf, counted in code points
{ $indexOfCP: [ <string>, <substring>, <start>, <end> ] }
found → first code point index (0, 1, 2, …)
not found → -1
null / missing string → null
non-string (not null) → pipeline error
Pass [ "$field", "needle" ] to get the first code point index.
Optionally add start (and end) as non-negative code point 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 code point 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 // code point offset
to = end or lengthCP(s)
result = first code point 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 "cafétéria". Code points count é as one character; UTF-8 bytes count it as two.
Calculation
text = "cafétéria"
code points: c a f é t é r i a
indexes: 0 1 2 3 4 5 6 7 8
$indexOfCP(text, "é") → 3 (first é)
$indexOfCP(text, "t") → 4
$indexOfCP(text, "é", 4) → 5 (second é)
$indexOfCP(text, "e") → -1 (plain "e" is different)
Contrast ($indexOfBytes):
"t" is at byte 5 because é uses 2 UTF-8 bytes.
Prefer $indexOfCP when you mean “character position.”
Code
MongoDB Examples
We use a small messages collection with a text field. First we find a substring’s code point index, then we search from a start position. 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.
Do not treat -1 as a cut point. Branch with $cond when the substring might be missing.
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
$indexOfCP
O(string length) per doc
Character positions; i18n text
+ start / end
Smaller scan
Skip a known prefix / window
$indexOfBytes
Similar order
UTF-8 byte offsets
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 $indexOfCP often.
Remember
Key Takeaways
Role: first code point index of a substring.
Not found: returns -1 (not an error).
Range: optional start / end are code point offsets.
Bytes? use $indexOfBytes instead of code points.
One line:$indexOfCP answers “at which character does this substring start?”—or -1 if missing.
Frequently Asked Questions
$indexOfCP returns the zero-based Unicode code point index of the first occurrence of a substring inside a string. Use it inside aggregation expression stages such as $project and $addFields.
Use { $indexOfCP: [ <string>, <substring>, <start>, <end> ] }. start and end are optional code point offsets that limit the search range.
It returns -1 when the substring is not present in the string (within the optional start/end code point range).
$indexOfCP counts Unicode code points (characters). $indexOfBytes counts UTF-8 bytes. For plain ASCII they match; for accented letters or emoji the indexes can differ.
If the string expression is null or refers to a missing field, $indexOfCP returns null. A non-string (and not null) value causes an error. A null substring also errors.
Use $indexOfCP when you want human character positions—especially with international text—or when pairing with $substrCP. Use $indexOfBytes when you need UTF-8 byte offsets.