buf.includes()
Tests whether a buffer contains a given string, buffer, or byte value, returning true or false.
buf.includes(value[, start[, end]][, encoding])- Added in Node.js v5.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$value | string | Buffer | Uint8Array | integer | What to search for. An integer is treated as a single byte value. |
$startoptional | integer= 0 | Where to begin searching in buf. If negative, the offset is calculated from the end of buf. |
$endoptional | integer= buf.length | Where to stop searching in buf (exclusive). Added in Node.js v26.1.0. |
$encodingoptional | string= 'utf8' | If value is a string, this is its encoding. |
Return value
booleantrue if value was found in buf, false otherwise.
Errors
Throws ERR_INVALID_ARG_TYPE if value is not a string, Buffer, Uint8Array, or number, and ERR_UNKNOWN_ENCODING if encoding is not a recognised encoding name.
Changelog
- PHP v26.1.0Added the end parameter, so the search can be bounded on both sides.
- PHP v25.5.0, v24.13.1Supports a plain Uint8Array as the this value.
buf.includes() is a thin wrapper over buf.indexOf(): it runs the same byte search and returns indexOf(...) !== -1. Reach for it when you only care whether a sequence is present and never need its position — scanning a chunk for a delimiter, checking an uploaded file for a magic number, or asserting that a response body carries a marker.
Matching happens on bytes, not characters
A string value is first encoded using encoding, and those bytes are searched for. That has two consequences people trip over. First, a match can start in the middle of a multi-byte character, because nothing aligns the search to character boundaries. Second, the encoding must match how the buffer was built: Buffer.from('é').includes('é', 'latin1') is false, because the buffer holds the two UTF-8 bytes c3 a9 while the search term was encoded to the single Latin-1 byte e9.
Integers are single bytes
Passing a number searches for one byte, so buf.includes(97) looks for 'a'. Values outside 0–255 are coerced by value & 255 rather than rejected, so 353 quietly searches for 97.
Streaming caveat
A delimiter split across two chunks will not be found by either chunk. Keep a tail of delimiter.length - 1 bytes and prepend it to the next chunk before searching.
Example
When to use it
- Check whether an HTTP chunk already contains the \r\n\r\n header terminator before parsing.
- Verify an uploaded file starts with the expected magic bytes before trusting its extension.
- Scan a log buffer for an error marker without needing the byte offset.
- Test whether a binary protocol frame carries an optional trailer tag.
Frequently asked questions
What is the difference between buf.includes() and String.prototype.includes()?
buf.includes() compares bytes while String.prototype.includes() compares UTF-16 code units. A buffer search can match starting in the middle of a multi-byte character, and it depends on the encoding used to build both the buffer and the needle. Converting a buffer to a string with toString() before searching is safe for text but wasteful for large binary data and wrong for data that is not valid UTF-8.
Why does buf.includes() return false when I can see the text in the buffer?
Almost always an encoding mismatch: the needle string is encoded with the default 'utf8' while the buffer holds those characters in a different encoding, so the byte sequences differ. Print both buf and Buffer.from(needle, encoding) to compare the actual bytes. Passing a Buffer as the value instead of a string removes the ambiguity.
How do I limit buf.includes() to part of a buffer?
Pass a start offset to skip a prefix, and on Node.js v26.1.0 or later pass an end offset to bound the far side. On older versions the third argument is interpreted as encoding, so search a slice instead: buf.subarray(start, end).includes(value). subarray shares memory with the original buffer, so it does not copy.
More examples
Encoding mismatches produce false negatives
The same visible character gives opposite answers depending on the encoding used to turn it into bytes. `includes` never transcodes for you, so the needle's encoding has to match the encoding the buffer was created with. When you only know the byte pattern, pass a Buffer and skip the question entirely.
const { Buffer } = require('node:buffer');
const buf = Buffer.from('café');
console.log(buf);
// <Buffer 63 61 66 c3 a9>
// The buffer holds UTF-8 bytes, so a UTF-8 needle matches.
console.log(buf.includes('é'));
// true
// Encoded as latin1, 'é' is the single byte 0xe9, which is not in the buffer.
console.log(buf.includes('é', 'latin1'));
// false
// Search by raw bytes when you are unsure of the text encoding.
console.log(buf.includes(Buffer.from([0xc3, 0xa9])));
// trueFinding a delimiter across streamed chunks
Calling `includes` on each chunk in isolation returns false for both chunks here, because neither contains all four delimiter bytes. Keeping the last `DELIM.length - 1` bytes and prepending them to the next chunk makes the straddling match visible, which is the standard fix for any byte-sequence scan over a stream.
import { Buffer } from 'node:buffer';
import { Readable } from 'node:stream';
const DELIM = Buffer.from('\r\n\r\n');
// Chunk boundaries can split the delimiter, so carry a tail forward.
async function hasHeaderEnd(stream) {
let tail = Buffer.alloc(0);
for await (const chunk of stream) {
const window = Buffer.concat([tail, chunk]);
if (window.includes(DELIM)) return true;
tail = window.subarray(Math.max(0, window.length - (DELIM.length - 1)));
}
return false;
}
async function main() {
// The delimiter straddles the two chunks: '\r\n' ends one, '\r\n' starts the next.
const stream = Readable.from([
Buffer.from('GET / HTTP/1.1\r\nHost: example\r\n'),
Buffer.from('\r\nbody'),
]);
console.log(await hasHeaderEnd(stream));
// true
}
main();includes() versus indexOf()
`includes` is implemented as `indexOf(...) !== -1`, so the two run the identical search. If the next thing you do is slice around the match, call `indexOf` once and reuse the offset rather than calling `includes` first and then `indexOf` again — the second call rescans the buffer for no benefit.
const { Buffer } = require('node:buffer');
const frame = Buffer.from('id=42;name=node;ok=1');
// includes() answers a yes/no question.
console.log(frame.includes('name='));
// true
// indexOf() gives you the position, which you need in order to slice.
const at = frame.indexOf('name=');
console.log(at);
// 6
const end = frame.indexOf(';', at);
console.log(frame.subarray(at + 'name='.length, end).toString());
// node
// includes() is exactly this comparison, so calling both duplicates the scan.
console.log(frame.indexOf('name=') !== -1);
// true
Discussion