8.5 Ambient Declarations — Declaring Types Without Implementation
What are Ambient Declarations?
Ambient declarations use the declare keyword to provide type information only, without implementation. They tell TypeScript "this value exists somewhere" — in the runtime, an external library, or injected by a bundler.
// ✅ Ambient declaration: types only, no implementation
declare const API_URL: string;
declare function fetch(url: string): Promise<Response>;
declare class EventEmitter { }
// ❌ Regular declaration: includes implementation
const API_URL = 'https://api.example.com';
function fetch(url: string) { /* implementation */ }
class EventEmitter { /* implementation */ }
Types of declare Keywords
declare const / let / var
// Global variable declarations
declare const VERSION: string;
declare let currentUser: User | null;
declare var __ENV__: 'development' | 'production' | 'test';
// Usage
console.log(VERSION); // ✅ Type: string
declare function
// Global function declarations
declare function require(id: string): any;
declare function parseInt(s: string, radix?: number): number;
declare function setTimeout(callback: () => void, ms: number): number;
declare class
// Global class declaration
declare class MySDK {
constructor(config: SDKConfig);
initialize(): Promise<void>;
track(event: string, data?: Record<string, unknown>): void;
destroy(): void;
}
declare enum
// Enum declaration that disappears after compilation
declare enum Direction {
Up,
Down,
Left,
Right,
}
declare type / interface
// Types and interfaces can be used without declare, but for consistency:
declare type Callback = (err: Error | null, result?: unknown) => void;
declare interface EventHandler {
(event: MouseEvent): void;
capture?: boolean;
}
declare module
Full Type Declaration for External Modules
Used when a JavaScript library has no types.
// src/types/legacy-analytics.d.ts
declare module 'legacy-analytics' {
export interface TrackEvent {
name: string;
properties?: Record<string, string | number | boolean>;
userId?: string;
}
export function initialize(apiKey: string): void;
export function track(event: TrackEvent): void;
export function identify(userId: string, traits?: Record<string, unknown>): void;
export function page(name?: string): void;
export default {
initialize,
track,
identify,
page,
};
}
Quick Type Declaration (using any as a stopgap)
A fast way to resolve errors in urgent situations:
// src/types/quick-fix.d.ts
declare module 'untyped-lib'; // All exports become any
declare module 'another-untyped-lib' {
const lib: any;
export default lib;
}
Non-JS File Module Declarations
Used when importing images, CSS, SVG, etc. via webpack, Vite, etc.
// src/types/assets.d.ts
// Image files
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.svg' {
import React from 'react';
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement> & { title?: string }
>;
const src: string;
export default src;
}
// CSS Modules
declare module '*.module.css' {
const styles: Record<string, string>;
export default styles;
}
declare module '*.module.scss' {
const styles: Record<string, string>;
export default styles;
}
// JSON files (when tsconfig lacks resolveJsonModule)
declare module '*.json' {
const value: unknown;
export default value;
}
// Text files
declare module '*.txt' {
const content: string;
export default content;
}
// WASM
declare module '*.wasm' {
const buffer: ArrayBuffer;
export default buffer;
}
declare global
Used to declare global types inside module files (files with import/export).
// src/types/global.d.ts
import type { Logger } from '../utils/logger';
// Global declarations go inside declare global
declare global {
// Global variables
const __DEV__: boolean;
const __VERSION__: string;
// window extension
interface Window {
logger: Logger;
analytics: Analytics;
}
// Node.js global type extension
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
}
}
}
export {}; // Empty export to make this a module file
declare namespace
Groups types using namespaces. Especially useful for global libraries loaded via <script> tags.
// Type declarations for jQuery-style global libraries
declare namespace $ {
function ajax(url: string, settings?: AjaxSettings): JQueryXHR;
function get(url: string): JQueryXHR;
interface AjaxSettings {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: Record<string, unknown>;
success?: (data: unknown) => void;
error?: (error: Error) => void;
}
interface JQueryXHR {
done(callback: (data: unknown) => void): this;
fail(callback: (error: Error) => void): this;
always(callback: () => void): this;
}
}
// Global function overloads
declare function $(selector: string): JQuery;
declare function $(callback: () => void): JQuery;
Nested Namespaces
declare namespace MyApp {
namespace API {
interface Request {
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
}
interface Response<T = unknown> {
data: T;
status: number;
message: string;
}
}
namespace Auth {
interface User {
id: string;
email: string;
role: 'admin' | 'user';
}
interface Session {
user: User;
token: string;
expiresAt: Date;
}
}
}
// Usage
const req: MyApp.API.Request = {
url: '/users',
method: 'GET',
};
const session: MyApp.Auth.Session = { /* ... */ };
Real-World Project Type File Structure
src/
└── types/
├── assets.d.ts # Image, CSS, SVG module declarations
├── env.d.ts # process.env types
├── global.d.ts # Global types (window, utility types)
├── express.d.ts # Express module augmentation
└── vendor.d.ts # Third-party libraries without types
env.d.ts Example (Vite Project)
// src/types/env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
readonly VITE_STRIPE_KEY: string;
readonly VITE_GA_ID?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
// Usage
const apiUrl = import.meta.env.VITE_API_URL; // ✅ string
const gaId = import.meta.env.VITE_GA_ID; // ✅ string | undefined
Triple-Slash Directives
Special comments at the top of a file that declare type references.
/// <reference types="node" /> // Include @types/node
/// <reference lib="es2022" /> // Include ES2022 lib
/// <reference path="./custom.d.ts" /> // Reference specific file
Primarily used in .d.ts files.
// my-lib.d.ts
/// <reference types="node" />
declare module 'my-lib' {
import { EventEmitter } from 'events'; // Use Node.js types
class MyLib extends EventEmitter {
constructor(options: MyLibOptions);
start(): Promise<void>;
}
interface MyLibOptions {
port: number;
host?: string;
}
export = MyLib;
}
Pro Tips
1. declare module '*' — Wildcard module declaration
// Treat all unknown modules as any (last resort)
declare module '*';
2. When to use ambient vs regular declarations
| Situation | Choice |
|---|---|
| Value already exists at runtime | declare (ambient) |
| Value TypeScript will compile | Regular declaration |
| External JS library types | declare module |
| webpack/Vite processed files | declare module '*.ext' |
3. export = vs export default difference
// CJS-style library
declare module 'my-cjs-lib' {
function create(): Instance;
export = create; // module.exports = create
}
// ESM-style library
declare module 'my-esm-lib' {
function create(): Instance;
export default create; // export default create
}
4. Webpack plugin type declaration pattern
// Global constants injected by bundler
declare const __VERSION__: string;
declare const __BUILD_TIME__: string;
declare const __COMMIT_HASH__: string;