JavaScript Error Monitoring

"includes is not a function": Causes and Fixes

This TypeError means the value isn't the array or string you assumed. Learn the type-confusion patterns behind it, plus the legacy-browser and NodeList traps.

The includes is not a function error occurs when you call .includes() on a value that isn't an array or string. While it's less common than similar errors like map-is-not-a-function, it's just as disruptive when it happens — especially in production codebases that work with DOM elements, Sets, Maps, or dynamically shaped API responses. The root cause is always the same: includes exists only on Array.prototype and String.prototype, not on objects, NodeLists, or other collection types.

Understanding what types support includes and how to detect mismatches will save you hours of debugging and make your defensive code more robust.

What Causes "Includes Is Not a Function"

The error message is direct: you're calling .includes() on something that isn't an array or string. Here are the most common culprits:

It's an object, not an array: You're trying to check membership in an object instead of an array.

const data = { userId: 1, status: 'active' };
data.includes('active'); // TypeError: data.includes is not a function

It's a NodeList or HTMLCollection: DOM queries return NodeList or HTMLCollection, which have forEach but no includes.

const elements = document.querySelectorAll('.button');
elements.includes(document.querySelector('.button')); // TypeError: elements.includes is not a function

It's a Set or Map: Sets and Maps use .has(), not .includes().

const tags = new Set(['react', 'javascript', 'node']);
tags.includes('react'); // TypeError: tags.includes is not a function

It's undefined or null: The value never arrived because an async operation didn't complete or a property path is wrong.

let data;
// data not assigned (undefined)
data.includes('value'); // TypeError: Cannot read properties of undefined

It's a number: You're trying to check if a number contains a substring.

const count = 42;
count.includes('4'); // TypeError: count.includes is not a function

It's an arguments object: The arguments pseudo-array doesn't have includes (though arguments has array-like properties).

function checkArgs() {
  arguments.includes('test'); // TypeError: arguments.includes is not a function
}

Root Causes & Type Validation

JavaScript is dynamically typed, so at runtime a variable might not be what your code assumes. This is especially problematic in data transformation pipelines or when working with the DOM, where type uncertainty breeds bugs.

Use static type checking with TypeScript to catch these errors at compile time. TypeScript will warn you if you call .includes() on a type that doesn't support it before the code even runs.

In untyped or loosely typed code, always validate the type before calling array methods. The Array.isArray() method is the safest check for arrays; for strings, check typeof:

const value = getSomeValue();

// Check if it's an array
if (Array.isArray(value)) {
  if (value.includes('target')) {
    console.log('Found in array');
  }
}

// Check if it's a string
if (typeof value === 'string') {
  if (value.includes('target')) {
    console.log('Found in string');
  }
}

How to Fix "Includes Is Not a Function"

Solution 1: Verify the Type Before Calling includes

Always validate that a value is actually an array or string before calling .includes():

const response = await fetch('/api/users').then(r => r.json());

if (Array.isArray(response)) {
  if (response.includes('admin')) {
    console.log('Admin found');
  }
} else if (typeof response === 'string') {
  if (response.includes('admin')) {
    console.log('Substring found');
  }
} else {
  console.error('Unexpected type:', typeof response);
}

Solution 2: Convert NodeList to Array

When working with DOM elements, convert NodeList or HTMLCollection to an array using Array.from() or the spread operator:

// Broken: NodeList has no includes
const buttons = document.querySelectorAll('button');
const targetButton = document.querySelector('button.primary');
buttons.includes(targetButton); // TypeError

// Fixed: Convert to array first
const buttonArray = Array.from(buttons);
if (buttonArray.includes(targetButton)) {
  console.log('Target button found in list');
}

// Or use the spread operator
const buttons2 = [...document.querySelectorAll('button')];
if (buttons2.includes(targetButton)) {
  console.log('Found');
}

Solution 3: Use .has() for Sets and Maps

Sets and Maps have .has(), not .includes():

// Broken: Sets don't have includes
const roles = new Set(['admin', 'user', 'guest']);
roles.includes('admin'); // TypeError

// Fixed: Use has() for Sets
if (roles.has('admin')) {
  console.log('User is admin');
}

// Maps also use has()
const config = new Map([['timeout', 5000], ['retries', 3]]);
if (config.has('timeout')) {
  console.log('Timeout configured');
}

Solution 4: Handle Undefined or Null with Optional Chaining

Defensively handle uncertain values using optional chaining:

const data = getSomeData(); // Might be undefined

// Instead of: data.includes('value') — which crashes if data is undefined
// Use optional chaining:
if (data?.includes?.('value')) {
  console.log('Found');
}

Optional chaining is a band-aid, not a fix. It silently hides the real bug — that your data shape is wrong. Use it only for truly optional values; for required data, fix the underlying data-flow issue instead.

Solution 5: Convert Arguments Object to Array

If you need includes on the arguments pseudo-array, convert it first:

function logIfFound(target) {
  // Broken: arguments doesn't have includes
  arguments.includes(target); // TypeError

  // Fixed: Convert to array
  const args = Array.from(arguments);
  if (args.includes(target)) {
    console.log('Target found in arguments');
  }

  // Or use rest parameters instead (modern approach)
  function logIfFound2(...args) {
    if (args.includes(target)) {
      console.log('Target found');
    }
  }
}

Includes vs indexOf: Understanding the Difference

Both includes and indexOf search for a value, but they behave differently with NaN:

const arr = [1, 2, NaN, 4];

// indexOf cannot find NaN (because NaN !== NaN)
console.log(arr.indexOf(NaN)); // -1 (not found)

// includes can find NaN (uses SameValueZero comparison)
console.log(arr.includes(NaN)); // true (found)

Use includes when you need to find NaN or want simpler boolean results. Use indexOf if you need the position or are supporting very old runtimes (before ES2015 for strings, ES2016 for arrays).

For backward compatibility with legacy browsers, indexOf is the universal fallback:

function safeContains(arr, value) {
  // Modern: use includes if available
  if (Array.isArray(arr) && arr.includes) {
    return arr.includes(value);
  }
  // Fallback: indexOf for older runtimes
  return arr.indexOf && arr.indexOf(value) > -1;
}

Legacy Browser Support

Array.prototype.includes was introduced in ES2016 (ES7), and String.prototype.includes in ES2015 (ES6). Older browsers — notably Internet Explorer and older Android versions — don't support includes at all.

If you need to support IE or old embedded webviews:

Option 1: Use indexOf instead

// IE-compatible
if (arr.indexOf(value) > -1) {
  console.log('Found');
}

Option 2: Use a polyfill

Include core-js in your bundler and set an appropriate browserslist target:

{
  "browserslist": ["last 2 versions", "not dead", "not IE 11"]
}

Or manually polyfill if needed:

if (!Array.prototype.includes) {
  Array.prototype.includes = function(value) {
    return this.indexOf(value) > -1;
  };
}

Modern projects rarely encounter this issue because build tools like webpack and Vite automatically polyfill based on your browserslist. If you're targeting old runtimes explicitly, be aware that includes may not exist.

Diagnosing the Error

When you hit this error, quickly identify the actual type of the problematic value:

const value = getSomeValue();

// Log the type, whether it's an array, and the constructor name
console.log('Type:', typeof value);
console.log('Is Array?', Array.isArray(value));
console.log('Constructor:', value?.constructor?.name);

Then check the stack trace to see exactly which line called .includes() and what came before. The line number in the error is your starting point; work backward through the code to understand why that variable wasn't what you expected.

Best Practices

  1. Always check the type before calling includes: Validate with Array.isArray() or typeof.

  2. Know the alternatives: Use .has() for Sets/Maps, convert NodeLists with Array.from(), handle arguments with rest parameters.

  3. Test edge cases: Include tests for null, undefined, objects, NodeLists, and wrapped arrays. See error tracking best practices for structured testing approaches.

  4. Use TypeScript for critical code: Type safety catches these errors at build time. Declare the expected type at API boundaries and on function parameters.

  5. Log and track in production: These errors are often data-shape mismatches that only surface under certain conditions. Proper error tracking captures the actual value and context. Read how to read a stack trace to correlate production errors with code changes.

  6. Use a linter rule: Configure ESLint to warn when .includes() might be called on an unsafe type. Many TypeScript strictness rules catch this automatically.

For deeper debugging strategies, check out how to debug production errors and cannot read properties of undefined — both cover data-shape issues that lead to method-not-found errors like this one.

Start tracking errors in minutes

Catch "includes is not a function" and other runtime errors in production with LightTrace. Point your Sentry SDK at LightTrace, get a free account with 5,000 events/month, and see every error with full stack traces, affected users, and GitHub source links. Start free.

When this error does slip through to production, immediate detection and visibility into the actual type and value that caused it is the difference between a quick fix and hours of debugging. LightTrace gives you that visibility automatically.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.