declare

Describes the type of something that already exists at runtime without emitting any code.

Signature
declare var name: Type;
declare let name: Type;
declare const name: Type;
declare function fn(arg: Type): ReturnType;
declare class Name { constructor(arg: Type); method(): void; }
declare namespace Name { function fn(): void; let value: number; }
declare module "module-name" { export function fn(): void; }
declare global { /* augmentations */ }
  • Added in TypeScript 1.0

Parameters

ParameterTypeDescription
$declarationvar | let | const | function | class | namespace | module | enum | globalThe kind of entity being described. Each form declares a type or a value binding with no implementation.
$bodyoptionalsignatures onlyAmbient declarations carry signatures but never implementations — no function bodies, no field initializers, no method bodies. Providing one is an error.

Errors

Errors at compile time if an ambient declaration includes an implementation, an initializer or a function body, and at runtime with a ReferenceError if the declared entity does not actually exist when the code runs.

Changelog

  • PHP 1.8`declare global` was added, letting a module augment the global scope instead of only describing it.
  • PHP 3.8Ambient enum members without an initializer are treated as computed rather than constant, so they cannot be used in contexts requiring a literal type.

declare is a promise, not a definition. It tells the compiler "something with this name and this type exists at runtime; trust me" and emits nothing. The compiler stops complaining; it does nothing to make the promise true.

When to reach for it

  • Globals you did not create. A variable injected by a bundler (declare const __BUILD_ID__: string), a script tag loaded from a CDN, or an analytics snippet.
  • Untyped JavaScript. Writing a .d.ts alongside a dependency that ships no types, using declare module "pkg".
  • Class fields with definite assignment elsewhere — declare on a class property tells the compiler the field exists on the base class and must not be re-emitted.

What trips people up

Because nothing is checked, a wrong ambient declaration is worse than none: the compiler will happily let you call a method that does not exist, and you find out as a TypeError in production. The other frequent surprise is scope. A .d.ts with no top-level import or export is a global script, and everything in it lands in the global scope; add a single export and it becomes a module, at which point the same declarations become invisible to the rest of your project.

Example

Example · typescript
// globals.d.ts — describing things a <script> tag and the bundler already provide.
declare const BUILD_ID: string;

declare function track(event: string, payload?: Record<string, unknown>): void;

declare namespace Analytics {
  let queueLength: number;
  function flush(): Promise<void>;
}

declare class Widget {
  constructor(selector: string);
  render(): void;
}

track("page_view", { build: BUILD_ID });
new Widget("#root").render();
console.log(Analytics.queueLength);

When to use it

  • Type a configuration constant that a bundler replaces at build time.
  • Write a .d.ts for an internal package that was never migrated to TypeScript.
  • Describe a third-party widget attached to window by a script tag.
  • Narrow an inherited class field's type without emitting a field that clobbers it.

Frequently asked questions

How do I tell TypeScript about a global variable?

Add `declare const myGlobal: SomeType;` to a .d.ts file that has no top-level import or export, which makes the file a global script so the declaration reaches your whole project. If the file must be a module, wrap the declaration in `declare global { }` instead.

What is the difference between declare and a normal declaration?

A `declare` declaration is ambient — it produces types only and emits no JavaScript, so it describes something that must already exist at runtime. A normal declaration both types the entity and creates it in the output.

Why does my declare in a .d.ts file not work?

The most common cause is that the file contains a top-level import or export, which turns it into a module so its declarations are scoped to that module rather than global. The other cause is that tsconfig's include or files array does not cover the file at all.

More examples

Typing a dependency that ships no types

The declare module block supplies the shape of a package that has no bundled types, so imports type-check normally. Nothing is verified against the real implementation, which is why the option names and the default export must be transcribed carefully from the package's documentation — a mistyped option here becomes a silent runtime bug.

Example · typescript
// types/legacy-slug.d.ts
declare module "legacy-slug" {
  export interface SlugOptions {
    lower?: boolean;
    separator?: string;
  }
  export default function slugify(input: string, opts?: SlugOptions): string;
}

// app.ts
import slugify from "legacy-slug";

const title = "Hello World Again";
console.log(slugify(title, { lower: true, separator: "-" }));
// hello-world-again

// Add "typeRoots" or include the .d.ts in tsconfig "include" so it is picked up.

declare on a class field to avoid re-emitting it

Without declare, redeclaring name in Dog emits a class field, and under useDefineForClassFields that field is defined as undefined after super() runs, wiping the value the base constructor just set. The declare modifier makes the redeclaration type-only, so the output shows "rex" instead of undefined.

Example · typescript
class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  // Narrow the inherited field's type WITHOUT emitting a field declaration
  // that would overwrite it with undefined under useDefineForClassFields.
  declare name: "rex" | "fido";

  constructor(name: "rex" | "fido") {
    super(name);
  }
}

const d = new Dog("rex");
console.log(d.name);
// rex

Discussion

  • Be the first to comment on this lesson.