Menu

TypeScript Namespace: What It Is and When to Use One

A TypeScript namespace groups values and types under one name and compiles to a plain object. Learn the syntax, how namespaces merge with each other and with functions and classes, why ES modules replaced them, and where you still meet them: declaration files and global augmentation.

This page includes runnable editors - edit, run, and see output instantly.

A namespace is a named block that groups functions, constants and types under one name. Members marked export are reachable as Name.member; the rest stay private to the block. At run time a namespace is a plain object.

What a Namespace Compiles To

Namespaces are one of the few TypeScript features that generate code. The block above becomes a function that fills in an object:

var Geometry;
(function (Geometry) {
    const TAU = Math.PI * 2;
    function circumference(radius) {
        return TAU * radius;
    }
    Geometry.circumference = circumference;
    function area(radius) {
        return Math.PI * radius ** 2;
    }
    Geometry.area = area;
})(Geometry || (Geometry = {}));

This is the pattern JavaScript code used before modules existed to avoid putting every name on the global scope. TAU is a local of the function, which is why other code cannot see it. Trying to read it is a compile-time error:

The compiler reports index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. Add export in front of const rate and both lines run.

Nesting, Merging and Aliases

Namespaces can nest, and two blocks with the same name merge into one. The Geometry || (Geometry = {}) in the output is what makes that work: the second block adds to the existing object. import X = A.B creates a short alias.

namespace A.B.C { } is shorthand for three nested blocks. The old spelling module Shop { } means the same thing but is now rejected with error TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.

Merging with Functions and Classes

A namespace can share its name with a function, class or enum and add members to it. This is still the cleanest way to describe a function that also carries properties, or a class with helpers attached.

For a class, a static method does the same job and is plain JavaScript. For a function, you can also skip the namespace and assign format.prefix = "$" right after the declaration; TypeScript tracks properties assigned that way.

Namespaces vs Modules

Before ES modules, a large TypeScript program was many script files sharing global namespaces, stitched together with /// <reference path="..." /> and compiled into one file with outFile. Modules replaced that: each file is its own scope, dependencies are explicit imports, and bundlers can drop unused exports. TypeScript 7 removed outFile (error TS5102), so the multi-file namespace setup is no longer a build option.

NamespaceModule
Unita named block in a filethe file itself
Scopeglobal unless inside a modulealways its own
Dependenciesimplicit, by load orderexplicit import
Outputan object built by a functionimport/export or require
Unused code removalbundlers keep every memberbundlers can drop unused exports
Runs under Node type strippingonly if it holds types onlyyes

Inside a module, wrapping everything in a namespace adds a second level of naming for no gain: importers would write Utils.Utils.format. Export the functions directly and let the importer choose import * as Utils from "./utils.js" if they want a prefix.

Where You Still Meet Namespaces

Declaration files use them to describe libraries that expose one global object, and to group types:

// jquery-like.d.ts: a global function that also has properties
declare function $(selector: string): unknown;
declare namespace $ {
    const version: string;
    function ajax(url: string): Promise<unknown>;
}

Type packages use them to expose types you can extend. @types/node declares namespace NodeJS, and adding to its ProcessEnv interface from any module needs declare global:

// env.d.ts
export {};

declare global {
    namespace NodeJS {
        interface ProcessEnv {
            API_URL: string; // process.env.API_URL is now string, not string | undefined
        }
    }
}

Interfaces inside merged namespaces merge too, which is what makes this augmentation work.

Namespaces and Type Stripping

Node 24 runs .ts files by deleting the type syntax. A namespace with values cannot be deleted, it has to be compiled into the object shown above, so node app.ts stops with:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode

Two kinds of namespace are fine because they vanish completely: a declare namespace, and a namespace whose members are all types or interfaces. The compiler option erasableSyntaxOnly: true reports the others at compile time as error TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., which is how projects that run on type stripping keep them out. node --experimental-transform-types does compile namespaces, with an experimental warning.

Frequently Asked Questions

Should I use namespaces or modules in TypeScript?

Use modules (import and export) for new code. Every file is already its own scope, bundlers and Node understand modules, and unused exports can be removed. Namespaces remain useful in declaration files, for global augmentation, and for attaching types or helpers to a function or class of the same name.

What does a TypeScript namespace compile to?

An object filled in by an immediately invoked function: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. Exported members become properties of that object; members without export stay local to the function.

What is the difference between namespace and module in TypeScript?

A module is a file with a top-level import or export. A namespace is a named block inside a file. Old TypeScript called namespaces "internal modules" and allowed module Foo {}; that spelling is now error TS1540, and only namespace Foo {} is accepted.

Can Node run TypeScript files that use namespaces?

Not with its default type stripping. A namespace that contains values produces ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, because stripping only deletes types and a namespace needs generated code. Namespaces that contain only types, and declare namespace, are erased and run fine.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED