Learn why TypeScript throws TS2709 when a namespace is used in a type position, and how to fix it with typeof, a qualified member type, or real declarations.
TypeScript error TS2709 means you wrote a name in a type position — after a colon, in extends, inside a generic argument — and that name turned out to be a namespace. The most common way to hit it is annotating a parameter with a namespace import: import * as UserApi from "./user-api" followed by function run(api: UserApi).
A namespace is a container. It groups exports under one name, but it does not describe a shape by itself, so there is nothing for the checker to compare a value against. TypeScript keeps types and namespaces in separate declaration spaces; when name resolution in a type position lands on a symbol that is only a namespace, it reports TS2709 rather than TS2304 ("cannot find name") — the name resolved fine, it simply is not a type.
Four kinds of declaration produce a namespace-only symbol: a namespace import (import * as X), a namespace X {} block that exports only values, a shorthand ambient module (declare module "x" with no body), and an export = declaration whose name is a namespace merged with a function. The fix is almost always one of two one-word changes: typeof X when you meant "the module object itself", or X.SomeType when you meant a type that lives inside the container.
// The general shape of the error:
// function removeAccount(api: UserApi) {}
// ~~~~~~~
// resolves to a namespace, not a type
// → Cannot use namespace 'UserApi' as a type.The mirror image is TS2749 ("only refers to a value, but is being used as a type here"), which fires for a plain variable or class instance used as a type. Both are fixed by typeof.
Injecting a whole module so a test can swap it out is a good pattern, and import * as UserApi is the right way to grab it. The annotation is where it goes wrong: UserApi is the container, not its type.
// ❌ Broken
import * as UserApi from "./user-api"
function removeAccount(api: UserApi, id: string) {
// ~~~~~~~ Error: Cannot use namespace 'UserApi' as a type.
api.deleteUser(id)
}typeof crosses from the value world into the type world. Applied to a namespace import it yields an object type with one property per export, so callers can pass the real module or a stub with the same surface.
// ✅ Fixed — typeof gives you the type of the module object
import * as UserApi from "./user-api"
function removeAccount(api: typeof UserApi, id: string) {
api.deleteUser(id)
}If what you actually wanted was a type the module exports, name it directly — UserApi.UserProfile — or import it: import type { UserProfile } from "./user-api".
declare module ShimWhen a package ships no declarations, the quickest way to silence TS7016 is a one-line shim. It works for the import — and then breaks the moment you try to annotate anything with it, because the shorthand form declares a namespace-shaped any and nothing else.
// ❌ Broken — shim.d.ts contains: declare module "sorted-array"
import SortedArray from "sorted-array"
let activeOrders: SortedArray
// ~~~~~~~~~~~ Error: Cannot use namespace 'SortedArray' as a type.Replace the shorthand with a real declaration body describing the parts you use. You get the annotation back and lose the implicit any that the shim was hiding.
// ✅ Fixed — shim.d.ts declares the actual shape
declare module "sorted-array" {
export default class SortedArray<T> {
constructor(items: T[])
insert(item: T): void
search(item: T): number
readonly array: T[]
}
}// ✅ Fixed — the annotation now refers to a real class type
import SortedArray from "sorted-array"
let activeOrders: SortedArray<string> = new SortedArray<string>([])
activeOrders.insert("order-1001")Before writing the shim, check whether @types/sorted-array exists on npm — published declarations are better maintained than a local file.
namespace Block That Exports Only ValuesA namespace declaration creates a type only if something inside it does. If it holds nothing but constants and functions, the name exists solely in the namespace space, and an annotation on it fails.
// ❌ Broken
namespace OrderValidation {
export const minTotal = 1
export function isValidTotal(total: number) {
return total >= minTotal
}
}
let rules: OrderValidation
// ~~~~~~~~~~~~~~~ Error: Cannot use namespace 'OrderValidation' as a type.Decide which of the two things you meant. If you want a type that belongs to the group, declare it inside and reference it qualified:
// ✅ Fixed — the type lives inside the namespace and is referenced with a dot
namespace OrderValidation {
export interface Rule {
field: string
message: string
}
export const minTotal = 1
export function isValidTotal(total: number) {
return total >= minTotal
}
}
const totalRule: OrderValidation.Rule = {
field: "total",
message: `Total must be at least ${OrderValidation.minTotal}`,
}If you really meant "the container object", use typeof again:
// ✅ Fixed — typeof describes the namespace object itself
function describeRules(rules: typeof OrderValidation) {
return `Minimum total: ${rules.minTotal}`
}export = Declaration From a Typed PackageOlder declaration files — and generated ones, such as the DFINITY didc output or @types/linkify-it — export a callable merged with a namespace of helper types. The name works as a value and as a qualifier, which makes it look like a type until you use it as one.
// ❌ Broken — the package's .d.ts ends with: export = LinkifyIt
import LinkifyIt = require("linkify-it")
let linker: LinkifyIt
// ~~~~~~~~~ Error: Cannot use namespace 'LinkifyIt' as a type.The value is a factory function, so the type you want is its return type. Types the package groups under the same name stay reachable through the dot.
// ✅ Fixed — derive the instance type, keep using the namespace as a qualifier
import LinkifyIt = require("linkify-it")
let linker: ReturnType<typeof LinkifyIt> = LinkifyIt()
const hits: LinkifyIt.Match[] = linker.match("see https://example.com") ?? []For a name that is new-able rather than callable, InstanceType<typeof LinkifyIt> is the equivalent. If the declarations are yours to change, exporting an interface with the same name alongside the namespace makes the plain annotation work for everyone downstream.
Decide what you meant: the container or something in it. Read the offending annotation and ask whether you want the module object (all its exports as properties) or one specific type it holds. Those are the only two sensible readings, and each has its own fix.
Use typeof X for the container. api: typeof UserApi is the canonical way to type an injected module, a mock in a test, or a namespace object passed around as a dependency. It is also the fix for a namespace block used as an annotation.
Use X.TypeName for a type inside it. Namespaces are qualifiers in type position: OrderValidation.Rule, LinkifyIt.Match, NodeJS.Timeout. If the dot gives you TS2694 instead, the namespace exists but the member does not — check the version of the declarations you resolved.
Derive the instance type for callables and constructors. ReturnType<typeof Factory> for a function that returns the object, InstanceType<typeof Ctor> for one you call with new. Both keep working if the package later changes its shape.
Replace shorthand declare module "x" with real declarations. The one-liner is a mute button, not a type; everything imported through it is any, so bugs pass the checker silently. Install @types/x if it exists, otherwise write out the handful of exports you actually use — and never reach for let value: any to end the argument, because that discards the checking you turned on TypeScript for.
Prefer a named type export in code you own. If downstream code keeps needing typeof to describe your module, export an interface or type that names the shape explicitly. The annotation then reads as what it is, and nobody has to rediscover this error.
You used a name in a type position that TypeScript resolved to a namespace rather than to a type. Namespace imports written as import * as Api, namespaces declared with the namespace keyword, and shorthand ambient modules all create containers, and a container describes no shape on its own.
The compiler reports TS2709 instead of "cannot find name" because the name does resolve, just not to something usable as a type. That is also why the error has survived unchanged since TypeScript 2.3 — it is a precise message about declaration spaces, not a lookup failure. A namespace that merges with an interface, a class, or an enum does not trigger it, because the merged partner contributes a real type:
enum ShippingSpeed {
Standard = "standard",
Express = "express",
}
namespace ShippingSpeed {
export function isExpress(speed: ShippingSpeed) {
return speed === ShippingSpeed.Express
}
}
const chosen: ShippingSpeed = ShippingSpeed.ExpressAnnotate the parameter with typeof followed by the namespace import. After import * as UserApi from "./user-api", the parameter type is typeof UserApi — the type of the module object, with every export as a property.
import * as UserApi from "./user-api"
function removeAccount(api: typeof UserApi, id: string) {
api.deleteUser(id)
}That is exactly what you want when injecting a module for testing: a stub object with the same exports satisfies the annotation structurally. If you only need one function, type the parameter as that function instead — deleteUser: (id: string) => void — so the test double stays small.
declare module "x" cause Cannot use namespace as a type?A shorthand ambient module — declare module "x" with no body — tells TypeScript the module exists but says nothing about its exports, so every import from it is a namespace-shaped any. Annotating with that import therefore hits TS2709.
The real fix is to replace the shorthand with a declaration body, or install the package's @types. If you need the build green before you can do that, InstanceType<typeof SortedArray> compiles against the shim and gives you a place to hang the eventual type:
import SortedArray from "sorted-array"
function firstOrder(list: InstanceType<typeof SortedArray>) {
return list.array[0]
}Treat that as a bookmark, not a solution: under the shorthand shim every one of those members is still any, so a typo in list.array will not be caught until runtime.
Browse all TypeScript practice challenges to keep sharpening your type-level skills.
Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.
Or start solving right away: explore all TypeScript challenges