This TypeScript error occurs when you try to redeclare a namespace in an ambient context (like in a .d.ts file). The fix involves understanding ambient declarations, using proper module augmentation patterns, or converting ambient declarations to modules.
The error "Namespace 'X' cannot be redeclared in ambient context" occurs in TypeScript when you attempt to redeclare a namespace that already exists in the global scope. This typically happens in declaration files (.d.ts) or files without import/export statements (ambient contexts). In TypeScript, an "ambient context" refers to: 1. **Declaration files (.d.ts)**: Files that describe types but contain no runtime code 2. **Global scripts**: Files without any import or export statements 3. **Module augmentation**: When you try to extend existing module declarations Namespaces in ambient contexts are treated as global declarations. Once a namespace is declared in an ambient context, you cannot redeclare it with the same name in another ambient context. This prevents conflicting global definitions but can be confusing when you're trying to extend or augment existing types. The error commonly appears when: - You have multiple .d.ts files that declare the same namespace - You're trying to augment a third-party library's namespace - You mix namespace declarations with module declarations - You have legacy TypeScript code using namespaces alongside modern ES modules
If your file doesn't need to be ambient (global), add an import or export statement to make it a module:
// BEFORE - ambient context (problematic)
declare namespace MyLibrary {
export interface Config {
timeout: number;
}
}
// AFTER - module context (isolated)
declare namespace MyLibrary {
export interface Config {
timeout: number;
}
}
// Add this line to make it a module
export {};The export {} statement tells TypeScript this is a module, not an ambient context. This isolates the namespace declaration to this file only.
If you need to extend an existing namespace from a third-party library, use module augmentation:
// WRONG - redeclaring ambient namespace
declare namespace Express {
interface Request {
user?: User;
}
}
// CORRECT - module augmentation
declare module "express" {
interface Request {
user?: User;
}
}
// OR for global namespace augmentation
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}Module augmentation uses declare module "module-name" syntax instead of redeclaring the entire namespace.
If you have multiple files declaring parts of the same namespace, consolidate them into one declaration file:
// types.d.ts - Single consolidated file
declare namespace MyApp {
export interface User {
name: string;
email: string;
}
export interface Config {
apiUrl: string;
timeout: number;
}
export type Status = "active" | "inactive" | "pending";
}TypeScript supports declaration merging for namespaces, but it's cleaner to have a single source of truth. Remove duplicate namespace declarations from other files.
Duplicate namespace declarations often come from conflicting type definition packages:
# List all @types packages
npm ls @types
# Check for multiple versions of the same package
npm ls @types/node
npm ls @types/express
# Remove and reinstall to deduplicate
rm -rf node_modules package-lock.json
npm installCommon conflicts occur with:
- @types/node (declares global NodeJS namespace)
- @types/express (declares Express namespace)
- Library-specific type definitions that declare global namespaces
If the conflict comes from third-party type definitions you can't control, enable skipLibCheck in tsconfig.json:
{
"compilerOptions": {
"skipLibCheck": true
}
}This skips type-checking for all declaration files, which can resolve namespace conflicts. However, this is a workaround—prefer fixing the root cause.
Warning: skipLibCheck disables type checking for all .d.ts files, which might hide other type errors.
Modern TypeScript projects should use ES modules instead of namespaces:
// OLD - namespace pattern
namespace Utilities {
export function formatDate(date: Date): string {
return date.toISOString();
}
}
// NEW - module pattern
// utilities.ts
export function formatDate(date: Date): string {
return date.toISOString();
}
// consumer.ts
import { formatDate } from "./utilities";Benefits of ES modules:
- No global namespace pollution
- Better tree-shaking and bundling
- Standard JavaScript module system
- Clearer dependency graph
### Ambient vs. Module Contexts
Understanding the difference is crucial:
Ambient Context:
- Files without import/export statements
- Declaration files (.d.ts) by default
- Declarations are global (pollute global namespace)
- Cannot redeclare namespaces
Module Context:
- Files with at least one import/export
- Isolated scope
- Can have multiple files with same namespace name
- Use export {} to convert ambient to module
### Namespace Declaration Merging
TypeScript allows namespace merging when declarations are compatible:
// File 1: api.d.ts
declare namespace MyLib {
export interface Request {
url: string;
}
}
// File 2: models.d.ts
declare namespace MyLib {
export interface User {
name: string;
}
}
// Result: MyLib contains both Request and User interfacesHowever, this only works when both files are ambient contexts. If one file becomes a module, merging fails.
### Global Augmentation Patterns
For augmenting global types (like adding to Window or NodeJS):
// global.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
CUSTOM_VAR: string;
}
}
interface Window {
myCustomAPI: any;
}
}
export {}; // Makes this a module for augmentationThe declare global block allows augmentation of global namespaces from within a module context.
### Third-Party Library Augmentation
When extending third-party libraries:
// express.d.ts
import "express";
declare module "express" {
interface Request {
user?: {
id: string;
role: string;
};
}
}Note: You must import the module first, then declare augmentation in the same file.
### Legacy Code Migration
Migrating from namespaces to modules:
1. Identify all namespace declarations
2. Convert each namespace to a module file
3. Update imports from `Namespace.export` to `import { export } from "./module"`
4. Add `export {}` to remaining .d.ts files to make them modules
5. Remove global namespace pollution
Tools like ts-morph can help automate this migration.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript