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.
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:
-
TypeScript reads:
declare const API_URL: string;It records that a constant named
API_URLexists and that its type isstring. -
TypeScript does not expect an implementation here. There is no assigned value like:
const API_URL = "https://example.com"; -
In
buildUrl, TypeScript allows string concatenation becauseAPI_URLis known to be a string. -
When TypeScript emits JavaScript, the
declareline 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:
- TypeScript creates a type alias named
Callback. - This alias is used to type-check the
cbparameter. - The alias does not exist at runtime.
- 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.
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
| Concept | Purpose | Exists at runtime? | Emits JavaScript? | Typical use |
|---|---|---|---|---|
type Callback = ... | Create a type alias | No | No | Reusable type definitions |
declare type Callback = ... | Declare a type alias in declaration-style context | No | No | Mostly .d.ts or generated typings |
const value = ... | Create a real value | Yes | Yes | Normal application code |
declare const value: ... | Describe a real value that exists elsewhere | Yes, if provided externally | No declaration emitted | Globals, external libs, injected values |
function f() {} | Define a real function | Yes | Yes | Normal implementation |
declare function f(): void | Describe a function implemented elsewhere | Yes, if provided externally | No declaration emitted | Library typings, ambient declarations |
type vs declare const
type Id = string;
declare const API_URL: string;
Idis a type-only aliasAPI_URLis supposed to be a real runtime value, just not defined here
declare vs normal declarations
| Normal declaration | declare declaration |
|---|---|
| Provides implementation or value | Provides only type information |
| Used in application code | Often used in .d.ts files or external integrations |
| Produces runtime code when applicable | Does not produce runtime code |
| Safe by itself if implemented correctly | Unsafe at runtime if the external thing does not actually exist |
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
declaremeans: this exists elsewhere; use it for type checkingdeclaredoes not create runtime codetypeandinterfaceare already type-onlydeclareis most useful for values, functions, classes, modules, and globals.d.tsfiles commonly usedeclare
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
typealias 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.
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
apiBaseUrlandtimeoutproperties. - 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.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.