TS2503Semantic Error
Since TS 1.5

Fix TS2503: Cannot Find Namespace

Learn why TypeScript throws TS2503 when a qualified type like NodeJS.Timeout or JSX.Element finds no namespace in scope, and how to fix it properly.

error TS2503: Cannot find namespace 'X'

What This Error Means

TS2503 means you used a qualified type reference — a type name with a dot in it, like NodeJS.Timeout, JSX.Element or google.maps.Map — and the part before the dot did not resolve to any namespace in scope. TypeScript looked for a namespace called NodeJS, found nothing, and stopped there. It never even got as far as checking whether Timeout exists.

That distinction matters, because it tells you where to look. Namespaces like NodeJS, Express, JSX and google.maps are not built into the language; they are declared by declaration files that get loaded into the global scope. So the question is never "did I spell the type right" — it is "did the declarations that own this name actually make it into the program".

let debounceTimer: NodeJS.Timeout
                   ~~~~~~
error TS2503: Cannot find namespace 'NodeJS'.

Three neighbouring errors are worth telling apart, because they point at completely different fixes. TS2304 (Cannot find name 'X') is the unqualified version — one identifier, no dot. TS2694 (Namespace 'X' has no exported member 'Y') means the namespace was found and the member was not, so the package is installed but the shape changed. TS2702 ('X' only refers to a type, but is being used as a namespace here) means the left side resolved to something real that simply is not a namespace, which is what you get from Date.Foo or Math.Foo.

Common Causes

1. NodeJS.Timeout Without @types/node

By far the most common shape, and it almost always happens in browser code. setTimeout returns a number in the DOM and a NodeJS.Timeout object in Node, so people reach for the Node type to annotate a debounce timer — in a Vite or CRA project that never installed Node's declarations.

// ❌ Broken — @types/node is not installed
let debounceTimer: NodeJS.Timeout
//                 ~~~~~~
// Error: Cannot find namespace 'NodeJS'. (TS2503)
 
export function scheduleSave(save: () => void): void {
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(save, 400)
}
// ✅ Fixed — derive the type from the function instead of naming a namespace
let debounceTimer: ReturnType<typeof setTimeout>
 
export function scheduleSave(save: () => void): void {
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(save, 400)
}

ReturnType<typeof setTimeout> resolves to whatever setTimeout returns in this program — number under lib: ["dom"], NodeJS.Timeout under @types/node — so the same line compiles on both sides of the stack. Installing @types/node also works, and is the right call for server code, but in a frontend bundle it pulls every Node global into scope and invites process.env code that will not exist at runtime.

There is a second version of this cause that catches people with @types/node already installed: an explicit types array. Listing types switches off automatic loading of every @types package, and only the names you list survive.

// ❌ Broken — tsconfig.json; @types/node is installed but never loaded
{
  "compilerOptions": {
    "strict": true,
    "types": []
  }
}
// ✅ Fixed — name the package, or drop the array so everything auto-loads
{
  "compilerOptions": {
    "strict": true,
    "types": ["node"]
  }
}

"types": [] ships in a lot of Vite and library templates, and it makes every ambient namespace disappear at once — which is why the error often appears the moment you add a test runner or a new dependency, without you touching the offending line.

2. JSX.Element After Upgrading To React 19 Types

React 19's @types/react stopped declaring a global JSX namespace and moved it to React.JSX. Nothing about your component is wrong; the name it was written against no longer exists globally.

// ❌ Broken — @types/react 19, jsx: "react-jsx"
export function Avatar({ url }: { url: string }): JSX.Element {
  //                                              ~~~
  // Error: Cannot find namespace 'JSX'. (TS2503)
  return <img src={url} alt="" className="avatar" />
}
// ✅ Fixed — import the namespace from react
import type { JSX } from "react"
 
export function Avatar({ url }: { url: string }): JSX.Element {
  return <img src={url} alt="" className="avatar" />
}
// ✅ Also fine — skip the namespace entirely
import type { ReactElement } from "react"
 
export function Avatar({ url }: { url: string }): ReactElement {
  return <img src={url} alt="" className="avatar" />
}

React.JSX.Element works too if the file already imports React. Do not downgrade the types to make this go away, and do not re-declare a global JSX namespace yourself — a hand-written global will shadow React's and drift out of date. If a library you depend on still returns a bare JSX.Element, that failure surfaces inside its .d.ts file and needs a library update, not a change in your code.

A related trap: JSX types only load when the jsx compiler option is set and the file is a .tsx. A component pasted into a .ts file has no JSX namespace to find, no matter which React version you are on.

3. Express.Request Without @types/express

Node backends annotate middleware against the ambient Express namespace, which @types/express contributes. Install the runtime package only — or run npm ci with --omit=dev in a CI job — and the namespace is gone.

// ❌ Broken — express is installed, @types/express is not
export function currentUserId(req: Express.Request): string {
  //                                ~~~~~~~
  // Error: Cannot find namespace 'Express'. (TS2503)
  return String(req.headers["x-user-id"])
}
// ✅ Fixed — install @types/express, then import the type from the module
import type { Request } from "express"
 
export function currentUserId(req: Request): string {
  return String(req.headers["x-user-id"])
}

Importing Request is the part people skip, and it matters. The global Express namespace exists mainly as an augmentation slot: declaration merging targets it so that declare global blocks can add fields like req.user. It is not the full request type, so annotating with Express.Request after installing the types trades TS2503 for a TS2339 on req.headers. Use the global namespace to extend the type, and the module import to reference it.

4. A Custom .d.ts The Program Never Loads

When the namespace is one you declared yourself — a hand-written wrapper for a script-tag SDK such as google.maps, chrome or an internal legacy bundle — TS2503 means the declaration file is not part of the program at all.

// ❌ Broken — src/mapPanel.ts; types/google-maps.d.ts exists but is outside "include"
export function centerOn(map: google.maps.Map, lat: number, lng: number): void {
  //                          ~~~~~~
  // Error: Cannot find namespace 'google'. (TS2503)
  map.setCenter({ lat, lng })
}
// ✅ Fixed — tsconfig.json, so the declaration file is compiled with your sources
{
  "compilerOptions": {
    "strict": true
  },
  "include": ["src/**/*", "types/**/*.d.ts"]
}

Any .d.ts inside include contributes its globals — you do not need typeRoots or a folder shaped like an @types package. If you do set typeRoots, remember it replaces the default instead of extending it, so ./node_modules/@types has to stay in the list or every installed type package drops out at once and you trade one TS2503 for a dozen.

How to Fix It

  1. Identify who owns the namespace before changing anything. NodeJS comes from @types/node, Express from @types/express, JSX from @types/react (or another JSX runtime), google.maps from @types/google.maps or your own file. The owner determines the fix; everything below is just how you get that owner into the program.

  2. Install the declarations, then reference the type the way the package intends. npm install --save-dev @types/node or @types/express is the fix for a genuinely missing package. Where the library exports the type as a module member — Express's Request, React's JSX — import it instead of reaching for the global, because the global is usually only the augmentation surface.

  3. Check the types array in tsconfig.json. If it is present, it is an allowlist: only the packages named there are loaded, no matter what is installed. Add the missing name, or delete the array to restore automatic loading. Run npx tsc --showConfig when the file looks fine — it prints the resolved config after every extends, which is the only reliable way to see a types value inherited from a shared base config in a monorepo.

  4. Prefer a portable type over an ambient namespace. ReturnType<typeof setTimeout> instead of NodeJS.Timeout, React.ReactElement instead of a global JSX.Element. These keep compiling when a dependency changes its type layout — exactly the change that broke React 19 users — and they are the only option in code shared between browser and server builds.

  5. Do not paper over it with any or a fake namespace. Annotating the parameter as any, or adding declare namespace Express {} to silence the lookup, removes the error and the type checking with it: the hand-written namespace shadows the real one, and the next error is a TS2694 or TS2339 on a member that does exist. Fix the resolution instead, and keep the declarations in devDependencies so a fresh npm ci reproduces a working program.

  6. Restart the TypeScript server after installing types. Editors cache the program, so a freshly installed @types package often does not register until the language service reloads — in VS Code, "TypeScript: Restart TS Server". If tsc passes but your runner disagrees, check whether it reads include at all; ts-node, for instance, ignores files and include unless you pass --files.

FAQ

What causes TypeScript error TS2503?

TS2503 fires on a qualified type reference where the namespace part did not resolve. In NodeJS.Timeout, TypeScript first looks up NodeJS as a namespace; if nothing in the program declares one, you get this error and the member after the dot is never checked. Ambient namespaces are contributed by declaration files loaded into the global scope, so the cause is always about which declarations reached the compiler: a missing @types package, a types array in tsconfig.json that leaves it out, a .d.ts sitting outside include, or a library that stopped declaring the namespace globally. If the namespace resolves and only the member is missing, you get TS2694 instead — that is a version-mismatch signal rather than a missing-package one.

How do I fix Cannot find namespace 'NodeJS' in a frontend project?

You have two good options, and the better one depends on where the code runs. In browser code, drop the dependency on the namespace:

[object Object]

That resolves to number in a DOM program and to NodeJS.Timeout in a Node program, so it survives being shared between the two. In server code, install the declarations with npm install --save-dev @types/node. If they are already installed and the error persists, look for "types" in tsconfig.json — an explicit array disables automatic loading of every other @types package, and "types": [] is common in frontend templates. Adding "node" to that array fixes it without touching a line of source.

How do I fix Cannot find namespace 'JSX' after upgrading to React 19?

React 19's type definitions removed the global JSX namespace and made it React.JSX, so any bare JSX.Element annotation loses its namespace the moment the types update. The smallest fix is a type-only import:

[object Object]

React.JSX.Element and React.ReactElement work equally well, and ReactElement is the most future-proof of the three since it does not depend on the namespace layout at all. Two things not to do: pinning @types/react back to 18 postpones the problem, and declaring your own global JSX namespace shadows React's with a definition that will drift. If the error comes from inside node_modules, a dependency is still emitting global JSX in its own .d.ts — upgrade that library rather than working around it in your code.

Related Errors

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Related Concepts

Share this reference

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges