Declaration Files (.d.ts): Typen fuer JS-Bibliotheken schreiben
<T>
type
TypeScript · Declaration Files · .d.ts · DefinitelyTyped
Declaration Files (.d.ts)
Typen fuer JS-Bibliotheken schreiben

Wer eine untypisierte JavaScript-Bibliothek einbindet, verliert Autovervollstaendigung und Typsicherheit im gesamten Projekt. Declaration Files schliessen diese Luecke, ohne den fremden Code zu veraendern. Dieser Guide zeigt, wann DefinitelyTyped bereits ausreicht, wann eigene Typen noetig sind und wie man das globale Window-Objekt sicher erweitert.

12 Min. Lesezeit .d.ts · DefinitelyTyped · Ambient Declarations TypeScript 5.x · npm · @types

1. Was .d.ts-Dateien sind und warum TypeScript sie braucht

Eine Declaration File, erkennbar an der Endung .d.ts, enthaelt ausschliesslich Typinformationen und keinen ausfuehrbaren Code. Der TypeScript-Compiler entfernt sie beim Build vollstaendig, zur Laufzeit existiert sie nicht. Ihre einzige Aufgabe ist, dem Compiler und dem Editor mitzuteilen, welche Form eine Variable, eine Funktion oder ein Modul hat, damit Autovervollstaendigung, Refactoring-Sicherheit und Fehlerpruefung auch fuer Code funktionieren, der selbst kein TypeScript ist.

Fuer eigene .ts-Dateien generiert der Compiler mit der Option declaration in der tsconfig.json automatisch passende .d.ts-Dateien neben dem kompilierten JavaScript. Das Problem entsteht, sobald eine Abhaengigkeit importiert wird, die selbst kein TypeScript ist und keine mitgelieferten Typen hat: Ohne Declaration File sieht der Compiler nur any, verliert jede Pruefung und meldet je nach strict-Konfiguration sogar einen Fehler wie TS7016, weil keine Type Definitions gefunden wurden.

2. DefinitelyTyped zuerst pruefen: @types-Pakete

Bevor eine eigene Declaration File geschrieben wird, lohnt sich immer ein Blick auf DefinitelyTyped, das groesste Community-Repository fuer TypeScript-Typen. Fuer tausende populaere JavaScript-Pakete ohne eigene Typen existiert dort ein passendes @types-Paket, gepflegt von der Community und ueber npm unter dem Scope @types veroeffentlicht. Ein einfacher npm install --save-dev @types/paketname reicht meist aus, damit der Compiler das Paket vollstaendig typisiert erkennt, ohne eine Zeile eigenen Code zu schreiben.

TypeScript findet Pakete unter node_modules/@types automatisch, ohne dass typeRoots in der tsconfig.json manuell gesetzt werden muss. Vor der Installation lohnt sich ein Blick in die package.json der Zielbibliothek selbst: Traegt sie bereits ein types- oder typings-Feld, liefert sie ihre Typen mit, und ein zusaetzliches @types-Paket waere ueberfluessig oder koennte sogar in Konflikt geraten.


# Check whether the target package already ships its own types
npm view lodash types

# Install community types from DefinitelyTyped
npm install --save-dev @types/lodash

# If the package itself ships types, no @types package is needed
npm view zod types

3. Wann man eigene Deklarationen schreiben muss

Eigene Declaration Files werden erst notwendig, wenn zwei Bedingungen gleichzeitig zutreffen: Das importierte Paket liefert selbst keine Typen, und auf DefinitelyTyped existiert kein passendes @types-Paket. Das betrifft haeufig kleine, veraltete oder unternehmensinterne Bibliotheken, die nie fuer TypeScript-Nutzer gepflegt wurden. Der Compiler meldet in diesem Fall typischerweise TS7016 (Could not find a declaration file) oder TS2307 (Cannot find module), abhaengig davon, ob ueberhaupt ein Modul gefunden wird.

Der zweite haeufige Anlass ist die Erweiterung globaler Browser- oder Node-APIs, die zwar zur Laufzeit existieren, aber nicht Teil der Standardbibliothek lib.dom.d.ts sind. Ein Tracking-Skript, das window.dataLayer bereitstellt, oder eine Konfiguration, die per Script-Tag als globale Variable injiziert wird, sind fuer den Compiler unsichtbar, bis eine Declaration File ihre Form beschreibt. Auch der Import von Nicht-JS-Assets wie .svg oder .css ueber Bundler-Loader benoetigt eine eigene Ambient-Deklaration, da TypeScript solche Dateiendungen von sich aus nicht kennt.

4. Basis-Syntax fuer Ambient Declarations

Das Schluesselwort declare markiert eine Deklaration als ambient: Sie beschreibt einen Wert, der bereits an anderer Stelle existiert, ohne selbst Code zu erzeugen. declare function und declare const beschreiben einzelne globale Werte, declare module oeffnet einen benannten Modulnamen, und declare namespace gruppiert zusammengehoerige Typen unter einem gemeinsamen Bezeichner. Innerhalb einer .d.ts-Datei ist declare vor Top-Level-Deklarationen technisch oft optional, macht die Absicht aber lesbar und wird deshalb in Style-Guides empfohlen.

Wichtig ist der Unterschied zwischen Script- und Modul-Kontext: Eine .d.ts-Datei ohne import oder export gilt als globales Script, und alle darin deklarierten Namen landen im globalen Namensraum. Sobald die Datei ein import oder export enthaelt, wird sie zum Modul, und globale Ergaenzungen muessen explizit in einem declare global-Block stehen. Dieser Unterschied ist eine der haeufigsten Fehlerquellen bei eigenen Declaration Files und wird in Abschnitt 8 genauer behandelt.


// global.d.ts or any ambient declaration file (script context, no import/export)

// Declare a function that exists at runtime but has no types
declare function gtag(command: string, ...args: unknown[]): void;

// Declare a constant injected by a script tag in index.html
declare const BUILD_VERSION: string;

// Declare an entire module with a minimal shape
declare module "legacy-lib" {
  export function init(options: { debug: boolean }): void;
}

// Group related ambient types under a namespace
declare namespace MyApp {
  interface Config {
    apiUrl: string;
    timeout: number;
  }
}

5. Typen fuer ein untypisiertes npm-Paket deklarieren

Fuer ein konkretes untypisiertes Paket ist declare module "paketname" { ... } das zentrale Werkzeug, auch bekannt als Module Augmentation. Innerhalb der geschweiften Klammern wird die oeffentliche API des Pakets nachgebildet, exakt wie sie tatsaechlich exportiert wird: benannte Exporte als export function oder export interface, ein Default-Export als export default. Die Deklaration muss nicht vollstaendig sein, es reicht, die tatsaechlich genutzten Teile der API zu typisieren, der Rest laesst sich spaeter ergaenzen, sobald er gebraucht wird.

Solche Dateien liegen ueblicherweise in einem eigenen Ordner wie types/ oder src/types/ und muessen im include-Array der tsconfig.json erfasst sein, damit der Compiler sie einliest. Wichtig: Die Datei selbst wird nirgends importiert, sie wirkt allein durch ihre Anwesenheit im Kompilierungskontext. Bei mehreren Paketen mit fehlenden Typen bietet sich eine eigene Datei pro Paket an, etwa types/legacy-lib.d.ts, statt alle Deklarationen in eine einzige globale Datei zu mischen.


// types/legacy-lib.d.ts
// Minimal type coverage for an untyped npm package

declare module "legacy-lib" {
  export interface LegacyLibOptions {
    debug?: boolean;
    retries?: number;
  }

  export function initialize(options: LegacyLibOptions): Promise<void>;

  export default class LegacyClient {
    constructor(apiKey: string);
    request(path: string): Promise<unknown>;
  }
}

6. Das globale Window-Objekt erweitern

Skripte, die per script-Tag im HTML geladen werden, etwa ein Tag-Manager oder eine serverseitig injizierte Konfiguration, haengen ihre Werte direkt an window. Die Standardbibliothek lib.dom.d.ts kennt diese projektspezifischen Eigenschaften naturgemaess nicht, weshalb window.dataLayer ohne Erweiterung als Fehler TS2339 gemeldet wird. Die Loesung ist Declaration Merging: TypeScript erlaubt, dieselbe Schnittstelle Window an mehreren Stellen zu deklarieren, wobei alle Deklarationen zu einer einzigen zusammengefuehrt werden.

Damit declare global innerhalb einer Datei funktioniert, muss diese Datei als Modul gelten, also mindestens ein import oder export enthalten, notfalls das leere export {} am Ende. Innerhalb von declare global { interface Window { ... } } werden dann nur die zusaetzlichen Eigenschaften notiert, nicht die gesamte bestehende Window-Schnittstelle. Nach dem Speichern erkennt der Compiler window.dataLayer und window.myAppConfig im gesamten Projekt als vollstaendig typisiert, inklusive Autovervollstaendigung im Editor.


// global.d.ts
// Extend the built in Window interface with app specific globals
export {}; // turns this file into a module so declare global works

declare global {
  interface Window {
    dataLayer: Record<string, unknown>[];
    myAppConfig: {
      apiBaseUrl: string;
      featureFlags: Record<string, boolean>;
    };
  }
}

// Usage anywhere in the project, fully typed
window.dataLayer.push({ event: "checkout_started" });
console.log(window.myAppConfig.apiBaseUrl);

7. Eigene .d.ts mit einem veroeffentlichten Paket ausliefern

Wer selbst ein npm-Paket veroeffentlicht, sollte Konsumenten dieselbe Typsicherheit bieten, die man selbst von Bibliotheken erwartet. Das Compiler-Flag declaration: true in der tsconfig.json erzeugt beim Build automatisch eine passende .d.ts-Datei neben jeder kompilierten .js-Datei, ganz ohne manuell geschriebene Deklarationen. Fuer ein Paket mit einem einzigen Einstiegspunkt reicht eine zentrale dist/index.d.ts, die alle oeffentlichen Typen re-exportiert.

Damit npm-Konsumenten diese Datei finden, muss das types-Feld (frueher typings) in der package.json auf den korrekten Pfad zeigen, ueblicherweise identisch zum Ordner des main-Feldes. Fehlt dieses Feld, sucht TypeScript automatisch nach einer .d.ts-Datei neben dem in main referenzierten Einstiegspunkt, was jedoch weniger robust ist als eine explizite Angabe, besonders bei komplexeren Ordnerstrukturen mit mehreren Unterpaketen.


{
  "name": "@mironsoft/build-utils",
  "version": "1.4.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc --declaration"
  },
  "files": [
    "dist"
  ]
}

8. Haeufige Stolperfallen bei Ambient Declarations

Der haeufigste Fehler ist das versehentliche Vermischen von Global- und Modul-Kontext: Eine Datei, die ein export {} vergisst, aber trotzdem declare global verwendet, wird vom Compiler stillschweigend ignoriert oder erzeugt kryptische Fehler an ganz anderer Stelle im Projekt. Ein zweiter klassischer Fehler ist declare module "*" oder ein zu breites Wildcard-Pattern fuer Asset-Importe: Es unterdrueckt zwar den Compiler-Fehler, liefert aber fuer jeden Import beliebigen Typs any zurueck und damit keine echte Typpruefung mehr.

Version-Drift zwischen einer installierten Bibliotheksversion und ihrem @types-Paket ist eine weitere, schwer zu diagnostizierende Fehlerquelle: DefinitelyTyped-Pakete werden unabhaengig von der Bibliothek selbst versioniert und koennen nach einem Major-Update der Bibliothek veraltete oder falsche Signaturen enthalten. Direktes Bearbeiten von Typen in node_modules/@types loest das Problem nie dauerhaft, weil jede npm-Installation die Aenderung ueberschreibt. Die korrekte Loesung ist immer eine eigene Declaration File mit Module Augmentation, die im Versionskontrollsystem des Projekts landet.

9. Declaration-File-Ansaetze im Vergleich

Fuer nahezu jedes Typisierungsproblem gibt es einen schnellen, riskanten Ausweg und einen etwas aufwendigeren, aber tragfaehigen Ansatz. Die folgende Uebersicht zeigt die fuenf haeufigsten Entscheidungen bei Declaration Files und warum sich der aufwendigere Weg langfristig auszahlt.

Aufgabe Riskanter Ansatz Empfohlener Ansatz Vorteil
Unbekannter Bibliothekstyp any ueberall verwenden Minimale .d.ts mit echten Signaturen Autovervollstaendigung und Refactoring-Sicherheit bleiben erhalten
Import ohne Typen // @ts-ignore beim Import declare module "paket-name"; als Stub Fehler nur an der Modulgrenze unterdrueckt, nicht projektweit
Fehlerhafte Fremdtypen node_modules/@types direkt bearbeiten Eigene .d.ts mit Module Augmentation Aenderung uebersteht npm install und Deployments
Zugriff auf window.myProp (window as any).myProp interface Window { myProp } via declare global Typsicherer Zugriff im gesamten Projekt, nicht nur punktuell
Vor dem Schreiben eigener Typen Direkt eigene .d.ts schreiben Erst DefinitelyTyped/@types pruefen Keine doppelten oder abweichenden Community-Typen

Der gemeinsame Nenner aller riskanten Ansaetze in der Tabelle: Sie verschieben das Problem, statt es zu loesen. any und @ts-ignore unterdruecken die Fehlermeldung, aber nicht die zugrunde liegende Unsicherheit, sie tauchen als Laufzeitfehler wieder auf, sobald sich die tatsaechliche API aendert. Eine minimale, aber korrekte .d.ts-Datei kostet wenige Minuten und zahlt sich bei jedem kuenftigen Refactoring aus.

Mironsoft

TypeScript-Tooling, Build-Pipelines und typsichere Headless-Integrationen

TypeScript-Typsicherheit fuer euer Projekt?

Wir analysieren eure Build-Pipeline, ergaenzen fehlende Declaration Files fuer interne und externe Pakete und sorgen dafuer, dass Headless-Integrationen und Tooling-Skripte vollstaendig typsicher laufen, von DefinitelyTyped-Audits bis zu eigenen .d.ts-Bibliotheken.

Typen-Audit

DefinitelyTyped-Abdeckung pruefen und fehlende oder veraltete @types-Pakete identifizieren

Eigene Declaration Files

Module und Global Augmentation fuer interne Bibliotheken und Legacy-Pakete schreiben

Build-Tooling

tsconfig, declaration-Output und types-Feld fuer eigene npm-Pakete sauber einrichten

10. Zusammenfassung

Declaration Files schliessen die Luecke zwischen JavaScript-Code ohne Typen und einem TypeScript-Projekt, das auf vollstaendige Typsicherheit angewiesen ist. Der richtige erste Schritt ist praktisch nie das Schreiben einer eigenen .d.ts-Datei, sondern die Pruefung, ob DefinitelyTyped bereits ein passendes @types-Paket bereitstellt. Erst wenn diese Pruefung negativ ausfaellt, lohnt sich eine eigene Ambient Declaration, sei es als Module Augmentation fuer ein einzelnes Paket oder als Global Augmentation fuer eine Browser-API wie window.

Die Syntax selbst ist ueberschaubar: declare module, declare global und ein sauberer Unterschied zwischen Script- und Modul-Kontext decken die meisten Praxisfaelle ab. Wer eigene Pakete veroeffentlicht, sollte von Anfang an declaration: true setzen und das types-Feld in der package.json pflegen, damit Konsumenten dieselbe Typsicherheit erhalten, die man selbst von jeder guten Bibliothek erwartet.

Declaration Files (.d.ts) - Das Wichtigste auf einen Blick

DefinitelyTyped zuerst

npm install --save-dev @types/paketname pruefen, bevor eine eigene Declaration File geschrieben wird.

Module Augmentation

declare module "paketname" { ... } fuer einzelne untypisierte Pakete in einer eigenen types/-Datei.

Global Augmentation

declare global { interface Window { ... } } fuer eigene Properties auf window, mit export {} als Modulkennzeichnung.

Eigene Pakete

declaration: true im Build und ein korrektes types-Feld in der package.json fuer alle Konsumenten.

11. FAQ: Declaration Files (.d.ts) in TypeScript

1Was ist eine Declaration File (.d.ts)?
Enthaelt ausschliesslich Typinformationen ohne ausfuehrbaren Code. Beschreibt dem Compiler die Form von Werten, die bereits an anderer Stelle existieren, etwa in einer JavaScript-Bibliothek.
2Wann brauche ich eine eigene .d.ts-Datei?
Nur wenn ein Paket selbst keine Typen mitliefert und auf DefinitelyTyped kein passendes @types-Paket existiert, oder bei einer globalen Browser-API ausserhalb der Standardbibliothek.
3Wie finde ich heraus, ob ein Paket bereits Typen hat?
npm view paketname types zeigt das types-Feld der package.json. Fehlt es, lohnt sich die Suche nach @types/paketname auf npm oder auf DefinitelyTyped.
4@types-Pakete vs. mitgelieferte Typen?
Mitgelieferte Typen stammen vom Bibliotheksautor und sind synchron mit der Version. @types-Pakete werden unabhaengig gepflegt und koennen kurzfristig veraltet sein.
5Was bedeutet declare global und export {}?
declare global erweitert globale Typen innerhalb eines Moduls. Ein leeres export {} macht eine sonst importfreie Datei zu einem Modul und aktiviert damit declare global.
6any oder @ts-ignore statt echter Typen?
Beide unterdruecken nur die Fehlermeldung, nicht das Typproblem. Eine minimale, korrekte Declaration File ist meist mit wenig Aufwand moeglich und sicherer.
7Wie deklariere ich window.dataLayer?
Mit export {} und einem declare global { interface Window { dataLayer: ... } }-Block. TypeScript fuehrt das per Declaration Merging mit Window zusammen.
8Wo sollten eigene .d.ts-Dateien liegen?
Ueblich ist ein Ordner wie types/, erfasst im include-Array der tsconfig.json. Bei mehreren Paketen empfiehlt sich eine Datei pro Paket.
9Zwei .d.ts-Dateien mit demselben Typ?
Interfaces werden per Declaration Merging zusammengefuehrt, solange die Signaturen kompatibel sind. Bei Widerspruechen oder doppelten type-Aliassen meldet der Compiler einen Fehler.
10Eigene Typen mit einem npm-Paket ausliefern?
declaration: true in der tsconfig.json setzen und das types-Feld in der package.json auf die generierte Haupt-Deklarationsdatei zeigen lassen.