TypeError: X Is Not a Function (tested)
TypeError: X is not a function means X exists as a value but is not callable — usually undefined, sometimes a string, number, or object. This is different from calling a name that was never declared at all. We reproduced the three real causes in Node.js v24.21.0 and captured the exact wording for each.
1. It's not usually a typo'd function name
A genuinely misspelled, never-declared name throws a different error entirely:
undeclaredFn();
// ReferenceError: undeclaredFn is not defined
"X is not a function" means the opposite situation: X resolved to something — it's just not a function. We ran this directly:
const handlers = {};
handlers.onClick();
// TypeError: handlers.onClick is not a function
handlers.onClick is undefined because nothing ever assigned it — a common bug when a conditional branch that sets a handler never ran. The message always names the full expression you called (handlers.onClick), not just the final property.
2. Cause: a named import that doesn't exist
We built a tiny module that only exports bar:
// modA.mjs
export function bar() { return 'bar'; }
A static ESM import of a missing export is actually caught at load time, before any function call — Node analyzes the module graph first:
import { foo } from './modA.mjs';
foo();
// SyntaxError: The requested module './modA.mjs' does not provide an export named 'foo'
That's a SyntaxError, not the bug this guide is about. The real "is not a function" case shows up with CommonJS destructuring, which has no such check — a missing property just silently becomes undefined:
// modA.cjs
module.exports = { bar: function() { return 'bar'; } };
// caseCJS.cjs
const { foo } = require('./modA.cjs');
foo();
// TypeError: foo is not a function
We confirmed the same silent failure with dynamic import(), which returns a plain namespace object with no export check at destructure time:
const { foo } = await import('./modA.mjs');
foo();
// TypeError: foo is not a function
So: static import {x} fails loudly and early; require() destructuring and dynamic import() destructuring both fail silently until the call site. If you renamed an export or imported from the wrong file, this is the likeliest cause.
3. Cause: unbound this loses the method
Extracting a method from an object and calling it alone detaches it from its this. What error you get depends on strict vs. sloppy mode. In a CommonJS (sloppy-mode) file:
const obj = {
helper: function() { return 42; },
run: function() { return this.helper(); }
};
const { run } = obj;
run();
// TypeError: this.helper is not a function
In sloppy mode, a standalone call makes this fall back to an object (Node's module-level this/global, not undefined), so reading this.helper doesn't crash — it just silently returns undefined, which then fails at the call. We measured the same code as a class in an ES module (always strict mode) and got a different error instead:
class Widget {
helper() { return 42; }
run() { return this.helper(); }
}
const w = new Widget();
const { run } = w;
run();
// TypeError: Cannot read properties of undefined (reading 'helper')
In strict mode this is undefined when a method is called without its object, so the property read throws first — see Cannot Read Properties of Undefined for that error on its own. Same root cause (a detached method), two different exact messages depending on strict mode.
4. How to tell which one you've got
| Situation | Exact error we measured |
|---|---|
| Name never declared anywhere | ReferenceError: X is not defined |
| Property/variable never assigned a function | TypeError: handlers.onClick is not a function |
Static import of a missing named export | SyntaxError: ... does not provide an export named 'X' |
require() destructure of a missing export | TypeError: foo is not a function |
Dynamic import() destructure of a missing export | TypeError: foo is not a function |
| Detached method, sloppy mode | TypeError: this.helper is not a function |
| Detached method, strict mode / class | TypeError: Cannot read properties of undefined (reading 'helper') |
5. Fixes
- Check the import first. Log the imported value (
console.log(foo)) before calling it. If it'sundefined, the export name or path is wrong — check the module's actual export list, not what you assume it exports. - Prefer static
importover destructuredrequire()/dynamicimport(). We measured that only the static form catches a missing export immediately, before any code runs. - Bind methods you plan to detach. Use
run = run.bind(obj), an arrow-function class field (run = () => this.helper()), or call it asobj.run()instead of destructuring it out. - Guard conditional assignment. If a property is only sometimes set to a function (like
handlers.onClickabove), give it a default no-op so a missed branch doesn't crash a later call.
If the crash instead happens on a property read rather than a call, see Cannot Read Properties of Undefined. If it's deep recursion blowing the stack rather than a bad call target, see Maximum Call Stack Size Exceeded.
Frequently asked questions
Is 'X is not a function' the same as calling a misspelled function name?
No. We measured a truly undeclared name and got a different error entirely: ReferenceError: X is not defined. 'TypeError: X is not a function' means X does exist as a value, it's just not callable (usually undefined).
Why does my import look right but the imported thing isn't a function?
We measured two silent-failure paths: destructuring a missing named export from require() and from dynamic import() both give you undefined with no warning, and only fail when you call it (TypeError: foo is not a function). A static import {foo} from '...' catches a missing export immediately as a SyntaxError instead.
I extracted a method from an object and now it's 'not a function' — why?
You detached it from `this`. We measured that in sloppy/CommonJS code this falls back to a non-undefined object, so `this.helper` reads as undefined and fails at the call: 'this.helper is not a function'. In strict mode or a class, `this` itself is undefined, so you get a different error first: 'Cannot read properties of undefined (reading 'helper')'.
Why do I get two different errors for the same kind of bug?
Strict mode changes what `this` is when a method is called standalone. We measured `this` as undefined in strict/class code (crashes on the property read) versus a fallback object in sloppy CommonJS code (crashes only when you try to call the undefined property). Same bug, different exact wording.
What's the fastest way to confirm this is the bug?
Add `console.log(typeof theThingYouAreCalling)` right before the call that throws. 'undefined' confirms it; anything else (like 'string' or 'object') means you're calling a non-function value for a different reason.
Written with AI assistance. Measured on 2026-10-03 with Node.js v24.21.0 on Linux. Every snippet below was run as a real file in /tmp and the output is copied verbatim, not guessed. PlainHeaders is our extension; Node.js is free.