Ambient modules
Declares the shape of a module that has no TypeScript source, including wildcard patterns like "*.css".
declare module "pkg" {
export function f(): void;
export default value;
}
declare module "pkg"; // shorthand: everything is any
declare module "*.css" { // wildcard pattern
const classes: Record<string, string>;
export default classes;
}- Added in TypeScript 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$"pkg" | string literal | The module specifier as it is written in import statements — a bare package name, a relative path, or a pattern containing a single `*`. |
$bodyoptional | declaration block | Export declarations describing the module. Omitting the body entirely gives a shorthand declaration in which every import from that specifier is typed `any`. |
$*optional | wildcard | At most one `*` in the specifier, matching zero or more characters. Used for loader-driven imports such as `"*.css"`, `"*.svg"` or `"*!text"`. |
Errors
Raises "Ambient module declaration cannot specify relative module name" for a relative specifier at top level, and "Ambient modules cannot be nested in other modules or namespaces" when the declaration appears inside a namespace.
Changelog
- PHP 2.0Wildcard module declarations and shorthand ambient module declarations (`declare module "x";`) added.
- PHP 4.7`--moduleDetection` introduced; under `force` (the default for `--module esnext` with JSX) every file is a module, which turns `declare module "x"` into a module augmentation instead of an ambient declaration.
An ambient module declaration tells the compiler that a module specifier resolves to something, and describes its shape, without any implementation file backing it. This is what silences TS7016: Could not find a declaration file for module 'x'.
When to reach for it
- An untyped dependency. Write a
.d.tsin your project withdeclare module "legacy-lib" { ... }and describe only the parts you actually call. - Non-JavaScript imports handled by a bundler.
declare module "*.css","*.svg","*.graphql"— TypeScript knows nothing about your loader, so you assert the resulting type. - An escape hatch. The shorthand
declare module "messy-lib";types every import from it asany: unsafe, but it unblocks a build in one line.
What trips people up
The single biggest gotcha is file context. An ambient declaration only works in a script file — one with no top-level import or export. Add a stray export {} or an import at the top and the same declare module block silently becomes a module augmentation of an existing module, which then errors because the module does not exist. Keep your .d.ts free of top-level imports; if you need to reference a type from elsewhere, put the import inside the declare module body.
The file also has to be in the program — via include, files, or a triple-slash reference.
Example
// globals.d.ts — a script file: no top-level import/export
declare module "tiny-slug" {
import type { Options } from "tiny-slug/options"; // import goes INSIDE
export default function slug(input: string, o?: Options): string;
export const separator: string;
}
declare module "*.css" {
const classes: Record<string, string>;
export default classes;
}
// app.ts
import slug, { separator } from "tiny-slug";
import styles from "./button.css";
console.log(slug("Hello World"), separator, styles.primary);
// -> "hello-world" "-" "btn_primary_a1b2"When to use it
- Type a JavaScript-only npm package that ships no .d.ts and has no @types entry
- Declare *.css, *.svg or *.graphql so bundler-handled imports type-check
- Unblock a build with a one-line shorthand declaration while proper typings are written
- Describe a module injected by a runtime or a build step that has no file on disk
Frequently asked questions
How do I fix "Could not find a declaration file for module"?
Install the package's `@types/...` if one exists; otherwise add a `.d.ts` in your project with `declare module "pkg" { ... }` describing the API you use, and make sure that file is covered by tsconfig's `include`. A one-line `declare module "pkg";` works as a stopgap but types every import from it as `any`.
Why is my declare module block being treated as a module augmentation?
Because the containing file is a module — it has a top-level `import` or `export`, or `--moduleDetection force` is in effect. Remove the top-level import (move it inside the `declare module` body), or split the ambient declarations into their own file with no top-level imports or exports.
How do I make TypeScript accept importing a CSS or image file?
Add a wildcard ambient module in a `.d.ts` file: `declare module "*.css" { const classes: Record<string, string>; export default classes; }`. TypeScript does not know what your bundler produces, so this declaration is you asserting the runtime shape the loader hands back.
More examples
Typing an untyped package incrementally
You describe only the API you actually use; anything the package exports but you never call can be left out. Point tsconfig at the folder with `"typeRoots"` or simply `"include": ["src", "types"]`. Because the declaration lives in your repo, it can be tightened over time without waiting on the package author.
// types/legacy-cache.d.ts
declare module "legacy-cache" {
export interface CacheOptions {
maxEntries?: number;
ttlMs?: number;
}
export class Cache<V> {
constructor(options?: CacheOptions);
get(key: string): V | undefined;
set(key: string, value: V): void;
readonly size: number;
}
export function createCache<V>(options?: CacheOptions): Cache<V>;
}
// app.ts
import { createCache, type CacheOptions } from "legacy-cache";
const opts: CacheOptions = { maxEntries: 100, ttlMs: 60_000 };
const cache = createCache<number>(opts);
cache.set("answer", 42);
console.log(cache.get("answer"), cache.size);
// -> 42 1Shorthand versus wildcard, and what each costs you
The shorthand form buys silence at the cost of all type safety — `anything.whateverYouLike()` compiles because the import is `any`. The wildcard forms cost three lines each and give real types: `logoUrl` is a string, so `.endsWith` checks, and `raw` is `unknown`, forcing a narrowing step before use. Prefer wildcards; treat shorthand as temporary.
// shims.d.ts
declare module "chaotic-lib"; // shorthand: everything is any
declare module "*.svg" { // wildcard: precise
const url: string;
export default url;
}
declare module "*.json" {
const data: unknown;
export default data;
}
// app.ts
import anything from "chaotic-lib";
import logoUrl from "./logo.svg";
import raw from "./config.json";
anything.whateverYouLike().nonsense; // no error: typed any
console.log(logoUrl.endsWith(".svg"));
// -> true
console.log(typeof raw);
// -> "object"
Discussion