declare means "trust me", and TypeScript does. Nothing compares a .d.ts file with the JavaScript it describes, so a declaration that says number where the library actually returns a string is accepted everywhere. Your code then type-checks against the declaration, not the behaviour: here a variable typed number ends up holding the string "51", with no error at compile time or run time.
// price.js -- the real library
export function parsePrice(text) {
return text.replace("$", "").trim(); // returns a string
}
// price.d.ts -- a wrong declaration
export declare function parsePrice(text: string): number;
// main.ts
import { parsePrice } from "./price.js";
const total: number = parsePrice("$5") + 1;
console.log(total);
// tsc --strict: no errors
// node: 51 ("5" + 1, while TypeScript believes total is a number)A .d.ts file contains only type declarations — function signatures, interfaces, variable types — with no actual implementation. It lets a JavaScript library (which has no types of its own) be used from TypeScript with full autocomplete and compile-time checking. The community-maintained DefinitelyTyped project publishes these as @types/* npm packages for thousands of popular untyped JavaScript libraries.
A basic declaration file
declare tells TypeScript "this exists at runtime, trust me — here's its type" without providing (or requiring) an implementation in this file.
declare function greet(name: string): string;
declare interface Config {
apiKey: string;
timeout?: number;
}
declare const VERSION: string;The same trust applies to @types packages below: they are written by hand, separately from the library, and can lag behind or simply be wrong. When behaviour and types disagree, believe the behaviour and fix the declaration. Once it says string, the same line becomes a compile error (error TS2322: Type 'string' is not assignable to type 'number'.), and the real conversion has to be written:
// price.d.ts
export declare function parsePrice(text: string): string;
// main.ts
import { parsePrice } from "./price.js";
const total: number = Number(parsePrice("$5")) + 1;
console.log(total); // 6@types packages: types for existing JavaScript libraries
Installing an @types/* package adds nothing to your runtime bundle — it's a devDependency containing only .d.ts files, which TypeScript reads at compile time and then discards.
$ npm install lodash # the actual JavaScript library
$ npm install -D @types/lodash # its community-maintained type declarations// now this has full autocomplete and type-checking, even though
// lodash itself is plain untyped JavaScript:
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2);Writing your own for an untyped library
When no @types package exists for a library, a minimal ambient declaration (even just declare module "library-name";, treating it as any) unblocks usage, and can be refined incrementally with real type information as needed.
declare module "some-untyped-library" {
export function doSomething(input: string): number;
}