Skip to content

TypeScript - Namespaces

Namespaces are a TypeScript-specific way to organize code by grouping logically related functionalities. They were particularly important before the widespread adoption of standard ES Modules (import/export) as a means to avoid polluting the global scope and prevent naming conflicts, especially in larger applications.

Recommendation: For most modern TypeScript projects, ES Modules (import/export) are the preferred standard for code organization and dependency management. Namespaces are now less common but still exist and can be useful in specific scenarios.

  • Legacy Codebases: You might encounter them in older TypeScript projects.
  • Structuring Internal Code: Sometimes used within a larger library or application to group internal helpers or components without exposing them as separate modules.
  • Global Object Augmentation: Used in declaration files (.d.ts) to add properties or methods to existing global objects or libraries.
  • Simple Scripts / --outFile: When compiling multiple .ts files into a single JavaScript output file using the tsc --outFile option (less common with module systems).

A namespace definition begins with the namespace keyword followed by the namespace name. Anything intended to be used outside the namespace must be explicitly exported using the export keyword.

// --- validationUtils.ts ---
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
// Internal detail, not exported
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return emailRegex.test(s);
}
}
export function isString(value: any): value is string {
return typeof value === 'string';
}
}

To access exported members from outside the namespace, use the namespace name followed by a dot (.) and the member name.

// --- app.ts ---
// Assuming validationUtils.ts is compiled/included
const email = "test@example.com";
const validator: Validation.StringValidator = new Validation.EmailValidator();
if (validator.isValid(email)) {
console.log(`'${email}' is a valid email.`);
} else {
console.log(`'${email}' is not a valid email.`);
}
console.log(`Is 'hello' a string? ${Validation.isString('hello')}`); // true
// console.log(Validation.emailRegex); // Error: Property 'emailRegex' is not exported from namespace 'Validation'.

Namespaces can span multiple files. TypeScript treats identically named namespaces declared in different files as merged. To ensure the compiler knows about dependencies between files when not using ES modules, you often need:

  • Triple-Slash Reference Directives: /// <reference path="./other-file.ts" /> placed at the top of the dependent file.
  • Compilation Order or --outFile: Ensuring files are compiled in the correct order or bundled into a single file using tsc --outFile output.js file1.ts file2.ts ... (Note: --outFile requires "module": "System" or "AMD" in tsconfig.json and is incompatible with CommonJS, ESNext, etc.).
// --- interfaces.ts ---
namespace MyShapes {
export interface Shape {
getArea(): number;
}
}
// --- circle.ts ---
/// <reference path="interfaces.ts" />
namespace MyShapes {
export class Circle implements Shape {
constructor(public radius: number) {}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
}
// --- rectangle.ts ---
/// <reference path="interfaces.ts" />
namespace MyShapes {
export class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
getArea(): number {
return this.width * this.height;
}
}
}
// --- main.ts ---
/// <reference path="circle.ts" />
/// <reference path="rectangle.ts" />
const circle = new MyShapes.Circle(5);
const rect = new MyShapes.Rectangle(10, 4);
console.log(`Circle Area: ${circle.getArea()}`);
console.log(`Rectangle Area: ${rect.getArea()}`);
// Compile using: tsc --outFile dist/app.js main.ts

Again, this multi-file setup is much more elegantly handled by ES Modules without needing reference directives or --outFile.

You can define namespaces inside other namespaces.

namespace App {
export namespace Services {
export class ApiClient {
fetchData() { console.log('Fetching data...'); }
}
}
export namespace Utils {
export function log(message: string) { console.log(`LOG: ${message}`); }
}
}
const client = new App.Services.ApiClient();
client.fetchData();
App.Utils.log("App started.");

You can create shorter aliases for nested namespaces using import alias = Namespace.NestedNamespace;

import ServiceClient = App.Services.ApiClient;
import logUtil = App.Utils.log;
const client2 = new ServiceClient();
logUtil("Using alias.");

Note: This import = syntax is specific to importing namespaces and older module formats, distinct from the standard ES import keyword.

While namespaces were an important part of early TypeScript, ES Modules (import/export) provide a standardized, more powerful, and widely adopted mechanism for code organization in modern JavaScript and TypeScript. Use namespaces judiciously, primarily when interacting with legacy systems or specific declaration file patterns.