Question

What `declare` Means in TypeScript

typescripttypescript2.0

Question

What does the declare keyword mean in TypeScript?

For example, what is the difference between these two declarations?

type Callback = (err: Error | string, data: Array<CalledBackData>) => void;

and

declare type Callback = (err: Error | string, data: Array<CalledBackData>) => void;

I could not find clear documentation explaining the purpose of declare in TypeScript. What does it do, and when should it be used?

Short Answer

By the end of this page, you will understand what declare means in TypeScript, why it is used for type-only information, and how it differs from normal declarations. You will also see where it appears in .d.ts files, when it is useful in real projects, and what mistakes beginners commonly make.

Concept

declare tells TypeScript:

  • This thing exists somewhere else
  • Use this information for type checking
  • Do not generate JavaScript for it

TypeScript has two jobs:

  • check types while you write code
  • compile to JavaScript

Normally, when you write code, TypeScript may emit JavaScript output. But sometimes you want to describe something that already exists at runtime, such as:

  • a global variable loaded by another script
  • a function provided by a library
  • types from an external JavaScript package
  • browser or environment-specific globals

That is where declare is used.

For a type alias like this:

type Callback = (err: Error | string, data: Array<CalledBackData>) => void;

type already exists only in TypeScript's type system. It does not produce JavaScript anyway. So adding declare here usually does not change runtime behavior.

declare type Callback = (err: Error | string, data: Array<CalledBackData>) => void;

This says: "there is a type named Callback available for type checking." In practice, declare is much more commonly used with things like:

declare const VERSION: string;
declare function loadData(id: number): Promise<string>;
declare class ApiClient {
  get(url: string): Promise<string>;
}

These declarations describe values that exist elsewhere.

Why this matters:

  • it lets TypeScript understand JavaScript code and external libraries
  • it supports autocomplete and error checking
  • it separates type information from implementation
  • it is the foundation of declaration files such as .d.ts

Mental Model

Think of declare as a label on a box you are not allowed to open.

TypeScript is told:

  • "Trust me, this box exists"
  • "Here is what is inside, at least from a typing point of view"
  • "Do not try to build the box yourself"

Without declare, you usually provide the real thing:

const VERSION = "1.0.0";

With declare, you only describe it:

declare const VERSION: string;

So:

  • normal declaration = description + implementation
  • declare declaration = description only

For type, there is no runtime implementation anyway, so declare type is mostly about saying "this type is being declared for type purposes only," which is often redundant outside declaration-file contexts.

Take Quiz

Syntax and Examples

The declare keyword can be used with several kinds of declarations.

Common syntax

declare const appName: string;
declare let currentUserId: number;
declare function fetchUser(id: number): Promise<string>;
declare class Logger {
  log(message: string): void;
}

These tell TypeScript about values that exist somewhere else.

Example: external global variable

Imagine a script tag loads a library that creates a global variable named analytics.

declare const analytics: {
  track(eventName: string): void;
};

analytics.track("page_view");

TypeScript now knows analytics exists and what methods it has.

Example: type alias

type Callback = (err: Error | string, data: CalledBackData[]) => void;

This creates a type alias in TypeScript.

You may also see:

declare type Callback = (err: Error | string, data: CalledBackData[]) => void;

In many ordinary .ts files, this is unnecessary because type is already type-only. It is more likely to appear in declaration-style code or generated typings.

Example: declaration file style

In a .d.ts file, you describe APIs without implementing them:

declare function parse(input: string): number;
declare const version: string;

No JavaScript is generated from this. It is only for the type checker.

Your callback example

type Callback = (err: Error | string, data: CalledBackData[]) => void;

This is enough in most cases.

Use the type like this:

function runTask(cb: Callback) {
  cb("none", []);
}

Step by Step Execution

Consider this example:

declare const API_URL: string;

function buildUrl(path: string) {
  return API_URL + path;
}

const result = buildUrl("/users");

Here is what happens step by step:

  1. TypeScript reads:

    declare const API_URL: string;
    

    It records that a constant named API_URL exists and that its type is string.

  2. TypeScript does not expect an implementation here. There is no assigned value like:

    const API_URL = "https://example.com";
    
  3. In buildUrl, TypeScript allows string concatenation because API_URL is known to be a string.

  4. When TypeScript emits JavaScript, the declare line disappears.

The emitted JavaScript would effectively look like this:

function buildUrl(path) {
  return API_URL + path;
}
const result = buildUrl("/users");

That means the real API_URL must actually exist at runtime. If it does not, JavaScript will fail when the code runs.

Now compare with a type alias:

type Callback = (value: string) => void;

function useCallback(cb: Callback) {
  cb("done");
}

Step by step:

  1. TypeScript creates a type alias named Callback.
  2. This alias is used to type-check the cb parameter.
  3. The alias does not exist at runtime.
  4. The emitted JavaScript contains only the function implementation.

So type is already erased during compilation, which is why declare type often adds little in normal code.

Real World Use Cases

declare is useful in several practical situations.

1. Typing global variables from external scripts

A script loaded in HTML might create a global:

declare const Stripe: {
  init(key: string): void;
};

This lets your TypeScript code use Stripe safely.

2. Writing or consuming .d.ts files

Libraries often ship declaration files that describe their API:

declare function formatDate(date: Date): string;

This helps TypeScript users get autocomplete and type safety even if the library itself is plain JavaScript.

3. Gradually adding TypeScript to a JavaScript project

If runtime values already exist, you can declare them while migrating:

declare const legacyConfig: {
  apiBase: string;
  timeout: number;
};

4. Describing environment-provided values

For example, a build tool or host platform may inject globals:

declare const __BUILD_ID__: string;

5. Defining types for third-party code without source changes

If a package has poor or missing typings, you can describe only what you need using declarations.

Take Quiz

Real Codebase Usage

In real projects, developers usually use declare in these patterns.

Declaration files

The most common place is a .d.ts file:

declare module "legacy-lib" {
  export function connect(url: string): void;
}

This tells TypeScript how to type a library.

Global augmentation

When a runtime global exists but TypeScript does not know about it:

declare global {
  interface Window {
    myAppVersion: string;
  }
}

Then elsewhere:

console.log(window.myAppVersion);

Bridging JavaScript and TypeScript

During migration, teams often keep JavaScript implementations and add TypeScript declarations around them.

API surface description

For package authors, declaration files are a clean way to expose types without exposing implementation details.

Important practical rule

If you are writing the actual implementation in the same file, you usually do not need declare.

Use:

const version = "1.0.0";

not:

declare const version: string;

unless the value is truly provided elsewhere.

Guarding against runtime assumptions

A declared value is only a promise to the type system. Developers often pair declarations with runtime validation when the source is uncertain.

Common Mistakes

1. Thinking declare creates a real variable

Broken expectation:

declare const apiKey: string;
console.log(apiKey);

This compiles, but if apiKey does not actually exist at runtime, your program will fail.

Avoid this by using declare only for values provided elsewhere.

2. Using declare when you should write a real implementation

Incorrect:

declare function add(a: number, b: number): number;

If you are implementing the function yourself, write:

function add(a: number, b: number): number {
  return a + b;
}

3. Assuming declare type is always necessary

Usually unnecessary:

declare type UserId = string;

Usually enough:

type UserId = string;

Because type already exists only during type checking.

4. Confusing types and values

This is invalid thinking:

type User = { name: string };
console.log(User);

User is a type, not a runtime value.

Similarly, declare does not turn a type into a runtime object.

5. Declaring incorrect shapes

If your declaration does not match reality, TypeScript may approve code that later crashes.

Example:

declare const config: {
  port: number;
};

If the real runtime value is actually:

const config = { port: "3000" };

then your types are misleading.

6. Forgetting that declaration files are type-only

.d.ts files should describe APIs, not contain executable logic.

Comparisons

ConceptPurposeExists at runtime?Emits JavaScript?Typical use
type Callback = ...Create a type aliasNoNoReusable type definitions
declare type Callback = ...Declare a type alias in declaration-style contextNoNoMostly .d.ts or generated typings
const value = ...Create a real valueYesYesNormal application code
declare const value: ...Describe a real value that exists elsewhereYes, if provided externallyNo declaration emittedGlobals, external libs, injected values
function f() {}Define a real functionYesYesNormal implementation
declare function f(): voidDescribe a function implemented elsewhereYes, if provided externallyNo declaration emittedLibrary typings, ambient declarations

type vs declare const

type Id = string;
declare const API_URL: string;
  • Id is a type-only alias
  • API_URL is supposed to be a real runtime value, just not defined here

declare vs normal declarations

Normal declarationdeclare declaration
Provides implementation or valueProvides only type information
Used in application codeOften used in .d.ts files or external integrations
Produces runtime code when applicableDoes not produce runtime code
Safe by itself if implemented correctlyUnsafe at runtime if the external thing does not actually exist
Take Quiz

Cheat Sheet

// Type alias
type Callback = (err: Error | string, data: CalledBackData[]) => void;

// Declared value provided elsewhere
declare const API_URL: string;

// Declared function provided elsewhere
declare function fetchData(id: number): Promise<string>;

// Declared class provided elsewhere
declare class Client {
  get(url: string): Promise<string>;
}

Key rules

  • declare means: this exists elsewhere; use it for type checking
  • declare does not create runtime code
  • type and interface are already type-only
  • declare is most useful for values, functions, classes, modules, and globals
  • .d.ts files commonly use declare

When to use it

Use declare when:

  • a variable is injected globally
  • a JavaScript library exists but TypeScript needs typings
  • you are writing a declaration file
  • the implementation lives elsewhere

When not to use it

Do not use declare when:

  • you are defining the real implementation in the same file
  • you expect it to create the variable or function
  • you only need a normal type alias in regular TypeScript code

Quick answer for the original example

These are usually equivalent for practical use in normal code:

type Callback = (err: Error | string, data: CalledBackData[]) => void;
declare type Callback = (err: Error | string, data: CalledBackData[]) => void;

The second form is generally unnecessary unless you are in a declaration-oriented context.

FAQ

What does declare do in TypeScript?

It tells TypeScript that a variable, function, class, or other entity exists somewhere else and should be used only for type checking.

Does declare generate JavaScript code?

No. Declarations marked with declare are removed during compilation.

When should I use declare in TypeScript?

Use it when the real implementation is external, such as a global script, a JavaScript library, or a declaration file.

Is declare type necessary?

Usually no. A type alias is already type-only, so declare type is often redundant in regular .ts files.

What is an ambient declaration?

An ambient declaration is a declaration that describes something that exists at runtime but is defined elsewhere. declare is used for this.

Can declare cause runtime errors?

Yes. If you declare something that does not actually exist at runtime, TypeScript will be satisfied but JavaScript will fail.

Is declare mainly used in .d.ts files?

Yes. That is its most common use.

What is the difference between declare const x: string and const x = "hello"?

declare const x: string only describes x. const x = "hello" actually creates the value.

Take Quiz

Mini Project

Description

Create a small TypeScript example that uses a declared global configuration object. This demonstrates the real purpose of declare: telling TypeScript about a runtime value that is provided elsewhere.

Goal

Build a function that reads from an externally provided config object using declare for type safety.

Requirements

  • Declare a global configuration object with apiBaseUrl and timeout properties.
  • Write a function that builds a request URL from the config.
  • Write a function that prints the timeout value.
  • Show a mock runtime implementation for testing.
  • Use TypeScript types correctly without redefining the config shape in multiple places.
Take Quiz

Keep learning

Related questions

View more