型定義ファイル(d.ts)とは、JavaScriptで書かれたライブラリや、実行時にだけ存在する値について「どんな型か」だけを TypeScript に伝えるためのファイルです。 中身は型の宣言だけで、JavaScript には変換されません。import したライブラリに型定義が見つからないと error TS7016: Could not find a declaration file for module ... が表示されますが、これは @types を入れる → declare module を1行書く → 型を自分で書く、の3段階のどれかで解消できます。

この記事では、TS7016 のエラー全文を手元で再現したうえで、3段階の直し方をコピペできる形で示します。あわせて「d.ts を書いたのに効かない」ときの原因を1つずつ再現し、チェック表にまとめました。掲載しているエラー文と出力は、すべて TypeScript 5.9.3(tsc)で実際に実行して取得したものです。 主要な3点は npm の latest である TypeScript 7.0.2 でも同じ結果になることを確かめています。

型定義ファイル(d.ts)とTS7016の早見表

まず「いま起きていること」から対処を引けるようにまとめます。

症状・やりたいこと 対処 書くもの(最小形)
TS7016 が出た(npm のライブラリ) 段階1:@types を入れる npm i -D @types/lodash
@types が存在しない 段階2:1行で通す(中身は any) declare module "legacy-lib";
補完と型チェックも効かせたい 段階3:型を自分で書く declare module "legacy-lib" { export function ... }
d.ts を置いたのに TS7016 が消えない include に d.ts のフォルダを足す "include": ["src", "types"]
d.ts に import を足したら効かなくなった declare global で囲む、または import("...") 型を使う export {}; declare global { ... }
./logo.svg の import で TS2307 ワイルドカード宣言 declare module "*.svg" { ... }
window.xxx で TS2339 Window を拡張 interface Window { xxx: ... }
process.env.XXX に型を付けたい ProcessEnv を拡張 declare namespace NodeJS { interface ProcessEnv { ... } }
Express の req.userId で TS2339 Request を拡張 declare global { namespace Express { interface Request { ... } } }
自作の .js に型を付けたい 同じ名前の d.ts を隣に置く calc.js の隣に calc.d.ts
d.ts が読まれているか知りたい 読み込んだファイルを一覧 npx tsc -p . --listFiles

エラー番号ごとの意味も並べておきます(どれもこの記事の中で再現しています)。

エラー番号 出る場面 主な直し方
TS7016 import 先の JavaScript は見つかったが、型定義が見つからない @types を入れる/declare module を書く/include を見直す
TS2307 import 先そのものが見つからない(svg・css など) ワイルドカード宣言、moduleResolution の見直し
TS2339 Window や Request に無いプロパティを使った interface を拡張する
TS2304 グローバル変数の宣言が見えていない d.ts がグローバル扱いかを確認する
TS2591 / TS2580 process などの型が見えていない types・typeRoots の指定を見直す
TS2665 / TS2669 d.ts の中の書き方がモジュール扱いと合っていない export {} と declare global の組み合わせを直す

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TypeScript 型定義ファイル(d.ts)の書き方とTS7016の直し方」

確認に使った環境

項目 版
Node.js 22.22.2
TypeScript 5.9.3(一部を 7.0.2 でも確認)
lodash / @types/lodash 4.18.1 / 4.17.25
express / @types/express 5.2.1 / 5.0.6
@types/node 22.20.5

基準にした tsconfig.json は次のとおりです。以降の例は、断りがない限りこの設定で npx tsc -p . を実行しています。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src"]
}

「型定義が無いライブラリ」の例として、node_modules/legacy-lib に JavaScript だけの小さなパッケージを手で置きました(型定義なしのライブラリを再現するための架空のパッケージです)。

// node_modules/legacy-lib/index.js
exports.slugify = (s, opt) => String(s).trim().toLowerCase().replace(/\s+/g, (opt && opt.separator) || '-');
exports.version = '1.0.0';

TS7016を再現する(エラー全文)

lodash(@types を入れていない状態)と legacy-lib を import します。

// src/a.ts
import _ from "lodash";
import { slugify } from "legacy-lib";

console.log(_.chunk([1, 2, 3, 4], 2));
console.log(slugify("Hello World"));
$ npx tsc -p .
src/a.ts(1,15): error TS7016: Could not find a declaration file for module 'lodash'. 'node_modules/lodash/lodash.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`
src/a.ts(2,25): error TS7016: Could not find a declaration file for module 'legacy-lib'. 'node_modules/legacy-lib/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/legacy-lib` if it exists or add a new declaration (.d.ts) file containing `declare module 'legacy-lib';`

エラー文は3つの情報でできています。

エラー文の部分 意味
Could not find a declaration file for module 'lodash' lodash の型定義(d.ts)が見つからない
'node_modules/lodash/lodash.js' implicitly has an 'any' type JavaScript 本体は見つかった。このままだと any として扱うことになる
Try npm i --save-dev @types/lodash ... or add ... declare module 'lodash'; 直し方の候補が2つ書いてある

TS7016 は「ライブラリが無い」のではなく「型だけが無い」というお知らせです。 実行時の動作には関係しないので、型を用意すれば解消します。なお、このエラーは「暗黙の any」を知らせるものなので、noImplicitAny が有効(strict: true に含まれます)のときに出ます。同じコードを --noImplicitAny false で実行するとエラー0件で通りましたが、import したものがすべて any になり型チェックが働かなくなるため、次の3段階で型を用意するほうが安心です。

TS7016を3段階で直す

flowchart TD
    A[TS7016 が出た] --> B{"@types/パッケージ名 が<br>npm にある?"}
    B -- ある --> C[段階1: npm i -D で入れる]
    B -- ない --> D{補完や型チェックが<br>必要?}
    D -- まず通したい --> E[段階2: declare module を1行]
    D -- 必要 --> F[段階3: 使う関数の型を書く]
    E -. あとから育てる .-> F
段階 方法 手間 型の安全性 向いている場面
1 @types を入れる 小 高い @types が公開されているライブラリ
2 declare module "名前"; を1行 小 any になる すぐにビルドを通したいとき、試しに使うとき
3 使う部分の型を書く 中 自分で書いた範囲は高い 長く使うライブラリ、社内の JavaScript

段階1:@types を入れる

エラー文の案内どおり、@types/パッケージ名 を開発用の依存として入れます。

npm i --save-dev @types/lodash
$ npx tsc -p .
src/a.ts(2,25): error TS7016: Could not find a declaration file for module 'legacy-lib'. 'node_modules/legacy-lib/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/legacy-lib` if it exists or add a new declaration (.d.ts) file containing `declare module 'legacy-lib';`

lodash のエラーが消え、legacy-lib の1件だけになりました。@types のパッケージは node_modules/@types に入り、tsconfig を変えなくても自動で読み込まれます。 ライブラリ自体が型定義を同梱している場合(package.json に types がある場合)は、@types を入れなくても最初からエラーになりません。

段階2:declare module を1行書く

@types が無いライブラリは、d.ts を1つ作って次の1行を書きます。置き場所は include に含まれるフォルダ(ここでは src)です。

// src/legacy-lib.d.ts
declare module "legacy-lib";
$ npx tsc -p .
(エラー0件)

この書き方は「このモジュールは存在する。中身はすべて any」という宣言です。手早い反面、何を書いても通る点は知っておきましょう。次のコードは、存在しない nothing を import し、引数も戻り値の型も合っていませんが、エラー0件でした。

// src/b.ts
import { slugify, nothing } from "legacy-lib";

const n: number = slugify(123, 456);
nothing.anything();

段階3:使う関数の型を自分で書く

同じ d.ts を、波かっこ付きの形に書き換えます。全部を書く必要はなく、自分が使う関数だけで十分です。

// src/legacy-lib.d.ts
declare module "legacy-lib" {
  export interface SlugifyOptions {
    separator?: string;
  }
  export function slugify(text: string, options?: SlugifyOptions): string;
  export const version: string;
}

src/a.ts はエラー0件で通り、JavaScript に変換して実行した結果は次のとおりでした。

[ [ 1, 2 ], [ 3, 4 ] ]
hello-world

今度は、型に合わない使い方がきちんと検出されます。

// src/b.ts
import { slugify, nothing } from "legacy-lib";

const n: number = slugify("Hello World", { separator: "_" });
slugify(123);
src/b.ts(1,19): error TS2305: Module '"legacy-lib"' has no exported member 'nothing'.
src/b.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'.
src/b.ts(4,9): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

段階2では通っていた3か所が、段階3ではすべてエラーとして見つかりました。まず段階2で通し、よく使う関数から段階3に育てていく進め方が取り組みやすいです。

d.tsの書き方の基本(declareの一覧)

d.ts の中では、declare を付けて「実体は別の場所にある」ことを伝えます。よく使う形を一覧にします。

宣言したいもの 書き方 使う側
グローバル変数 declare const APP_VERSION: string; APP_VERSION
グローバル関数 declare function track(name: string): void; track("view")
グローバルな型 interface User { id: string } const u: User = ...
既存のグローバル型に追加 interface Window { appConfig: ... } window.appConfig
npm パッケージ(中身は any) declare module "legacy-lib"; import x from "legacy-lib"
npm パッケージ(型つき) declare module "legacy-lib" { export ... } import { slugify } from "legacy-lib"
拡張子ごとの import declare module "*.svg" { ... } import logo from "./logo.svg"
名前空間 declare namespace NodeJS { ... } process.env.API_URL

グローバル扱いとモジュール扱いの違い

d.ts を書くうえで最も大切なのが、次の決まりです。

ファイルの一番外側に import か export が1つでもあると、そのファイルは「モジュール」として扱われ、中の宣言はファイルの外から見えなくなります。 import も export も無いファイルは「スクリプト」として扱われ、宣言はそのままグローバルになります。

d.ts の状態 扱い 一番外側の interface Window {...} declare module "x" {...}
import / export が無い スクリプト(グローバル) 既存の Window に追加される 新しいモジュールの宣言になる
import / export がある モジュール そのファイル内だけの別の Window になる 既存モジュールへの「追加」として扱われる

この違いが「d.ts を書いたのに効かない」原因の多くを占めます。次の章で実際に再現します。

d.tsを書いたのに効かない原因チェック表

上から順に確かめると、切り分けが早く進みます。

No. 確かめること よく出るエラー 直し方
1 d.ts が include の対象フォルダにあるか TS7016 のまま include に d.ts のフォルダを足す
2 tsc src/a.ts のようにファイル名を渡していないか TS7016 のまま tsc -p . で実行する(ファイル名を渡すと tsconfig.json は読まれない)
3 declare module を書いた d.ts の外側に import / export がないか TS7016 のまま(内部では TS2665) 外側の import をやめ、import("...") 型を使う
4 グローバル宣言の d.ts に import / export がないか TS2304、TS2339 declare global { } で囲む
5 declare global だけ書いて export が無い状態になっていないか TS2304、TS2339(内部では TS2669) 先頭に export {}; を足す
6 types を空や一部だけにしていないか TS2591 必要な名前を types に足す
7 typeRoots に自作フォルダだけを書いていないか TS2580 ./node_modules/@types も並べる
8 typeRoots のフォルダに d.ts を直接置いていないか TS7016 のまま フォルダ/パッケージ名/index.d.ts の形にするか、include に足す
9 moduleResolution が node10 のまま、exports のサブパスを import していないか TS2307 node16 / nodenext / bundler にする
10 ライブラリの exports に types 条件があるか TS7016(There are types at ... と続く) ライブラリ側の更新、または declare module で補う
11 skipLibCheck が true で、d.ts 自体のエラーが隠れていないか 画面には出ない 一時的に --skipLibCheck false で実行する

以下、それぞれを再現します。

原因1:include にd.tsのフォルダが入っていない

段階3で作った d.ts を、src/ から types/ に移してみます。tsconfig.json の include は ["src"] のままです。

$ npx tsc -p .
src/a.ts(2,25): error TS7016: Could not find a declaration file for module 'legacy-lib'. 'node_modules/legacy-lib/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/legacy-lib` if it exists or add a new declaration (.d.ts) file containing `declare module 'legacy-lib';`

--listFiles で読み込まれたファイルを見ると(node_modules 配下を除いて表示)、src/a.ts しか出てきません。d.ts が読まれていないことがわかります。

$ npx tsc -p . --listFiles | grep -v node_modules
src/a.ts

include に types を足します。

{
  "include": ["src", "types"]
}
$ npx tsc -p .
(エラー0件)

$ npx tsc -p . --listFiles | grep -v node_modules
src/a.ts
types/legacy-lib.d.ts

--explainFiles を使うと、読み込まれた理由まで表示されます。

types/legacy-lib.d.ts
  Matched by include pattern 'types' in 'tsconfig.json'

declare module を書いた d.ts は、どこからも import されないので、include(または files)に入っていないと読まれません。 「置いたのに効かない」ときは、まず --listFiles に自分の d.ts が出てくるかを確かめるのが近道です(出力のパスは、見やすいように作業フォルダからの相対パスに置き換えています)。

原因2:tscにファイル名を渡している

include を直したあとでも、次のようにファイル名を付けて実行すると TS7016 が再び出ました(以降、TS7016 の2行目にある案内文は省略して載せます)。

$ npx tsc --noEmit --strict --esModuleInterop --module node16 src/a.ts
src/a.ts(2,25): error TS7016: Could not find a declaration file for module 'legacy-lib'. 'node_modules/legacy-lib/index.js' implicitly has an 'any' type.

コマンドラインでファイル名を渡すと tsconfig.json は使われず、include も効きません。 設定を使うときは npx tsc -p .(または引数なしの npx tsc)で実行します。

原因3:declare module のd.tsに import を足した

型を別ファイルに分けようとして、d.ts の先頭に import を書いた例です。

// types/options.d.ts
export interface SlugifyOptions {
  separator?: string;
}
// types/legacy-lib.d.ts(うまくいかない例)
import type { SlugifyOptions } from "./options";

declare module "legacy-lib" {
  export function slugify(text: string, options?: SlugifyOptions): string;
  export const version: string;
}
$ npx tsc -p .
src/a.ts(2,25): error TS7016: Could not find a declaration file for module 'legacy-lib'. 'node_modules/legacy-lib/index.js' implicitly has an 'any' type.

d.ts は include に入っているのに、TS7016 に戻ってしまいました。理由は --skipLibCheck false を付けるとわかります。

$ npx tsc -p . --skipLibCheck false
(TS7016 に加えて次の1行が表示される)
types/legacy-lib.d.ts(3,16): error TS2665: Invalid module name in augmentation. Module 'legacy-lib' resolves to an untyped module at 'node_modules/legacy-lib/index.js', which cannot be augmented.

外側に import があるため、このファイルはモジュール扱いになり、declare module "legacy-lib" が「新しい宣言」ではなく「既存の型への追加(augmentation)」と解釈されました。legacy-lib には元になる型が無いので追加できない、というのが TS2665 の内容です。skipLibCheck が true だと d.ts 自体のエラーは表示されないため、原因が見えにくくなります。

直し方は、外側の import をやめて import("...") 型を使う形です。

// types/legacy-lib.d.ts(直した形)
declare module "legacy-lib" {
  export function slugify(text: string, options?: import("./options").SlugifyOptions): string;
  export const version: string;
}
$ npx tsc -p . --skipLibCheck false
(エラー0件)

なお、import 文を declare module の波かっこの中へ移す方法は、相対パスでは使えませんでした(TS2439: Import or export declaration in an ambient module declaration cannot reference module through relative module name.)。相対パスの型を使うときは import("...") 型か、同じ d.ts の中に型を書く形にします。

原因4・5:グローバル宣言のd.tsに export がある/無い

グローバル変数と Window の拡張を使うコードです(--lib es2022,dom を付けて実行)。

// src/g.ts
console.log(APP_VERSION);
console.log(window.appConfig.apiBase);

4通りの d.ts で結果を比べました。

d.ts の書き方 結果
A:import / export なしで、そのまま宣言 エラー0件
B:A に export type Id = string; を1行足した TS2304、TS2339
C:export があり、宣言を declare global { } で囲んだ エラー0件
D:declare global { } だけで、export が無い TS2304、TS2339(skipLibCheck false で TS2669 も表示)
// A:スクリプト扱い(そのままグローバルになる)
interface Window {
  appConfig: { apiBase: string };
}
declare const APP_VERSION: string;
// B:export を1行足しただけで、宣言が外から見えなくなる
export type Id = string;

interface Window {
  appConfig: { apiBase: string };
}
declare const APP_VERSION: string;
src/g.ts(1,13): error TS2304: Cannot find name 'APP_VERSION'.
src/g.ts(2,20): error TS2339: Property 'appConfig' does not exist on type 'Window & typeof globalThis'.
// C:モジュール扱いのファイルからグローバルに宣言する
export type Id = string;

declare global {
  interface Window {
    appConfig: { apiBase: string };
  }
  const APP_VERSION: string;
}

D の形(declare global だけ)では、--skipLibCheck false で次のエラーが表示されました。

types/global.d.ts(1,9): error TS2669: Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

declare global はモジュール扱いのファイルの中でだけ使えます。 export するものが無いときは、先頭に export {}; を1行置いてモジュール扱いにします。まとめると次の2つの型のどちらかに揃えるのが確実です。

型 書き方 使いどころ
スクリプト型 import / export を書かず、そのまま宣言 他の型を import しなくてよいとき
モジュール型 export {};(または import)+ declare global { } 他のファイルの型を import して使いたいとき

原因6・7・8:types と typeRoots の指定

process.env を使う1行で確かめます(@types/node は導入済み)。

// src/n.ts
console.log(process.env.API_URL);
tsconfig の指定 結果
types・typeRoots とも指定なし エラー0件
"types": [] TS2591
"types": ["node"] エラー0件
"typeRoots": ["./types"] TS2580
"typeRoots": ["./types", "./node_modules/@types"] エラー0件
("types": [] のとき)
src/n.ts(1,13): error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

("typeRoots": ["./types"] のとき)
src/n.ts(1,13): error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

TypeScript 公式の TSConfig リファレンス(typeRoots の項)には、既定では見えている @types パッケージがすべて取り込まれること、typeRoots を指定するとそこに書いたフォルダ配下のパッケージだけが対象になることが書かれています。typeRoots に自作フォルダだけを書くと node_modules/@types が対象から外れるので、両方を並べます。

もう1つ、typeRoots には「フォルダの形」の決まりがあります。include を ["src"] だけにして、typeRoots に ./typings を指定した状態で比べました。

置き方 結果
typings/legacy-lib/index.d.ts(パッケージ名のフォルダ+index.d.ts) エラー0件。--explainFiles に Entry point for implicit type library 'legacy-lib' と表示
typings/legacy-lib.d.ts(フォルダ直下にファイルを置く) TS7016 のまま

typeRoots が見るのは「直下のフォルダ」で、直下に置いた d.ts ファイルは読まれません。 自作の d.ts は、typeRoots に頼らず include に入れる方法がわかりやすく、迷いにくいです。

原因9・10:moduleResolution と package.json の exports / types

最近のライブラリは、package.json の exports で公開するファイルを決めています。型定義がどこにあるかも、この exports と moduleResolution の組み合わせで決まります。確認用に、次の package.json を持つ架空のパッケージ modern-lib を用意しました(dist/ には index.js・index.mjs・index.d.ts・utils.js・utils.d.ts があります)。

{
  "name": "modern-lib",
  "version": "2.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": { "import": "./dist/index.mjs", "require": "./dist/index.js" },
    "./utils": { "types": "./dist/utils.d.ts", "default": "./dist/utils.js" }
  }
}
// src/m.ts
import { hello } from "modern-lib";
import { sum } from "modern-lib/utils";

console.log(hello(), sum(1, 2));
設定 modern-lib modern-lib/utils
module: commonjs + moduleResolution: node10 通る TS2307
module: node16 通る 通る
module: esnext + moduleResolution: bundler TS7016 通る
(node10 のとき)
src/m.ts(2,21): error TS2307: Cannot find module 'modern-lib/utils' or its corresponding type declarations.
  There are types at 'node_modules/modern-lib/dist/utils.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.

(bundler のとき)
src/m.ts(1,23): error TS7016: Could not find a declaration file for module 'modern-lib'. 'node_modules/modern-lib/dist/index.mjs' implicitly has an 'any' type.
  There are types at 'node_modules/modern-lib/dist/index.d.ts', but this result could not be resolved when respecting package.json "exports". The 'modern-lib' library may need to update its package.json or typings.

node10 は exports を読まないため、サブパスの modern-lib/utils を見つけられません。一方、bundler では exports の import 条件が選ばれて index.mjs に進み、その隣の index.d.mts を探しますが、用意されていないので TS7016 になりました。--traceResolution を付けると、この流れがそのまま表示されます(抜粋)。

======== Resolving module 'modern-lib' from 'src/m.ts'. ========
Explicitly specified module resolution kind: 'Bundler'.
Resolving in CJS mode with conditions 'import', 'types'.
Entering conditional exports.
Matched 'exports' condition 'import'.
Using 'exports' subpath '.' with target './dist/index.mjs'.
File name 'node_modules/modern-lib/dist/index.mjs' has a '.mjs' extension - stripping it.
File 'node_modules/modern-lib/dist/index.mts' does not exist.
File 'node_modules/modern-lib/dist/index.d.mts' does not exist.

exports があるパッケージでは、一番外側の types フィールドよりも exports の中の types 条件が使われます。 package.json を次のように直す(types を各条件の先頭に置く)と、bundler でもエラー0件になりました。

{
  "exports": {
    ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" },
    "./utils": { "types": "./dist/utils.d.ts", "default": "./dist/utils.js" }
  }
}

自分でライブラリを公開するときは、この形を目安にしてください。利用する側で出会った場合は、ライブラリの新しい版を確かめるか、declare module "modern-lib" { ... } を自分の d.ts に書いて補うことができます。

d.tsが読まれているか確かめるコマンド

コマンド わかること
npx tsc -p . --listFiles コンパイル対象になったファイルの一覧。自分の d.ts が出てくれば読まれている
npx tsc -p . --explainFiles 各ファイルが対象になった理由(include に一致、import された、@types から自動、など)
npx tsc -p . --traceResolution import ごとに、どのファイルをどの順番で探したか
npx tsc -p . --skipLibCheck false d.ts の中のエラー(TS2665、TS2669 など)を表示する
npx tsc -p . --showConfig 最終的に使われる設定と、対象ファイルの一覧(files)

--listFiles と --explainFiles の出力は node_modules 配下も含むので長くなります。grep -v node_modules や grep 自分のファイル名 で絞ると読みやすくなります。

画像やCSSのimportを通すワイルドカード宣言

バンドラー(Vite や webpack など)を使うと、画像や CSS を import できます。TypeScript はこれらの中身を知らないので、宣言が無いと TS2307 になります。

// src/w.ts
import logo from "./logo.svg";
import styles from "./app.module.css";
import "./reset.css";
import text from "./readme.txt?raw";

const url: string = logo;
const cls: string = styles.title;
console.log(url, cls, text);
src/w.ts(1,18): error TS2307: Cannot find module './logo.svg' or its corresponding type declarations.
src/w.ts(2,20): error TS2307: Cannot find module './app.module.css' or its corresponding type declarations.
src/w.ts(4,18): error TS2307: Cannot find module './readme.txt?raw' or its corresponding type declarations.

3行目の import "./reset.css"; は値を受け取らない import なので、エラーになっていません。モジュール名に * を1つ含めた宣言を書くと、パターンに合う import すべてに型が付きます。

// types/assets.d.ts
declare module "*.svg" {
  const src: string;
  export default src;
}

declare module "*.module.css" {
  const classes: { readonly [className: string]: string };
  export default classes;
}

declare module "*.css";

declare module "*?raw" {
  const content: string;
  export default content;
}

この d.ts を置くとエラー0件になりました。型も働いていて、const size: number = logo; を足すと TS2322(Type 'string' is not assignable to type 'number')が表示されます。

パターン import の例 付く型
"*.svg"(png・jpg・webp も同じ形) import logo from "./logo.svg" string(ファイルの URL)
"*.module.css" import styles from "./app.module.css" クラス名から文字列への対応表
"*.css" import "./reset.css" any
"*?raw" import text from "./readme.txt?raw" string

import したときに実際に何が入るか(URL か、コンポーネントか)はバンドラーの設定で決まります。ここで確かめたのは tsc の型チェックまでで、バンドラーでのビルドは未実行です。Vite のように型定義を同梱しているツールでは、ツールが案内する方法(vite/client の型を読み込むなど)を先に使い、足りない分だけ自分で宣言すると重複を避けられます。

Window・process.env・Express Requestの拡張テンプレート

既存の型にプロパティを足す、よく使う3つをまとめます。どれも「同じ名前の interface は自動で1つに合わさる」という TypeScript の仕組み(宣言のマージ)を使っています。

拡張したいもの 拡張する相手 d.ts の形 必要なもの
window.dataLayer など interface Window モジュール型(declare global)またはスクリプト型 lib に dom
process.env.API_URL など NodeJS.ProcessEnv スクリプト型 @types/node
req.userId など Express.Request モジュール型(declare global) @types/express
// types/window.d.ts
export {};

declare global {
  interface Window {
    dataLayer: unknown[];
    appConfig: { apiBase: string };
  }
}
// types/env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: "development" | "production" | "test";
    readonly API_URL: string;
    readonly PORT?: string;
  }
}
// types/express.d.ts
import "express";

declare global {
  namespace Express {
    interface Request {
      userId?: string;
    }
  }
}

次のコードで、置く前と置いた後を比べました。

// src/e.ts
import express from "express";

const app = express();
app.use((req, _res, next) => {
  req.userId = "u-001";
  next();
});
app.get("/me", (req, res) => {
  res.json({ userId: req.userId });
});

const url: string = process.env.API_URL;
const port: string = process.env.PORT;
const other = process.env.UNKNOWN_KEY;
if (process.env.NODE_ENV === "staging") {
  console.log(url, port, other);
}
window.dataLayer.push({ event: "view" });
(d.ts を置く前)
src/e.ts(5,7): error TS2339: Property 'userId' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
src/e.ts(9,26): error TS2339: Property 'userId' does not exist on type 'Request<{}, any, any, ParsedQs, Record<string, any>>'.
src/e.ts(12,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.
src/e.ts(13,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.
src/e.ts(18,8): error TS2339: Property 'dataLayer' does not exist on type 'Window & typeof globalThis'.

(3つの d.ts を置いた後)
src/e.ts(13,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.
src/e.ts(15,5): error TS2367: This comparison appears to be unintentional because the types '"development" | "production" | "test"' and '"staging"' have no overlap.

req.userId・API_URL・window.dataLayer のエラーが消えました。残った2件は、型を付けたからこそ見つかったものです。PORT は省略可能(?)と宣言したので string への代入が指摘され、NODE_ENV は3つの値だけと宣言したので "staging" との比較が指摘されています。宣言していない UNKNOWN_KEY は、これまでどおり string | undefined として使えます。

Express の Request は、express-serve-static-core モジュールを拡張する書き方でも同じ結果になりました。

// types/express.d.ts(別の書き方)
import "express-serve-static-core";

declare module "express-serve-static-core" {
  interface Request {
    userId?: string;
  }
}

ミドルウェアと /me だけにしたコードをポート 8465 で起動し、fetch で呼び出したところ、次の応答が返りました。

200 {"userId":"u-001"}

d.ts は型を足すだけで、実行時の値は用意しません。 req.userId に値を入れるミドルウェアや、環境変数そのものの設定は別に必要です。必ず入るとは限らない値は ? を付けて宣言しておくと、使う側で未設定の場合を考えた書き方に導けます。

自作のJavaScriptに型を付ける/d.tsを自動で作る

プロジェクト内の .js を import したときも、TS7016 になります。

src/c.ts(1,21): error TS7016: Could not find a declaration file for module './lib/calc'. 'src/lib/calc.js' implicitly has an 'any' type.
方法 やること 結果
隣に d.ts を置く src/lib/calc.js の隣に calc.d.ts を作る エラー0件
allowJs を使う tsconfig に "allowJs": true エラー0件(型は JavaScript から推論される)
// src/lib/calc.d.ts
export declare function add(a: number, b: number): number;

隣に置く d.ts は、declare module で囲まずに export declare ... をそのまま書きます。ファイル名(拡張子を除いた部分)を .js と同じにするのがポイントです。

反対に、TypeScript で書いたコードからは d.ts を自動で作れます。

npx tsc gen/price.ts --declaration --emitDeclarationOnly --outDir gen/out
// gen/out/price.d.ts(生成されたもの)
export interface Item {
    name: string;
    price: number;
}
export declare function total(items: Item[], taxRate?: number): number;

元の関数は taxRate = 0.1 という既定値つきの引数でしたが、d.ts では実装が取り除かれ、taxRate?: number という型だけが残っています。d.ts を手で書くのは「TypeScript で書かれていないもの」に型を付けるときで、TypeScript のコードなら tsc に作ってもらうのが確実です。

TypeScript 7.0.2でも確認したこと

2026年10月3日時点で、npm の typescript の latest は 7.0.2 でした(npm view typescript dist-tags で確認)。この記事の主要な点を 7.0.2 の tsc でも実行した結果です。

確認したこと 7.0.2 での結果
include が ["src"] だけのとき 5.9.3 と同じ文面の TS7016
include に types を足したとき エラー0件
"moduleResolution": "node10" を指定 TS5108: Option 'moduleResolution=node10' has been removed.

7.0.2 では node10 の指定そのものが使えなくなっているため、原因9の表の1行目は 5.9.3 だけの結果です。そのほかの検証(原因3〜8、ワイルドカード、拡張テンプレート)は 7.0.2 では未実行です。

よくある質問

Q. d.ts はどこに置けばよいですか? A. tsconfig.json の include に入っているフォルダならどこでも読み込まれます。types/ や src/types/ にまとめ、include に "types" を足す形がわかりやすいです。ファイル名は自由ですが、legacy-lib.d.ts・assets.d.ts・env.d.ts のように対象ごとに分けると、あとから探しやすくなります。

Q. declare module "x"; の1行と、// @ts-ignore はどちらがよいですか? A. 1行の declare module のほうが扱いやすいです。import する場所が何か所あっても宣言は1つで済み、あとで波かっこ付きの型に書き換えれば、使っている場所すべてに型が行き渡ります。

Q. skipLibCheck は false にしたほうがよいですか? A. 普段は true のままで問題ありません。d.ts が効かないときにだけ --skipLibCheck false を付けて実行すると、TS2665 や TS2669 のような d.ts 自体のエラーが表示され、原因の手がかりになります。

Q. エディタでは赤線が出ないのに、tsc ではエラーになります(またはその逆)。 A. エディタは「開いているファイルに一番近い tsconfig.json」を使い、d.ts を追加した直後は前の状態を保持していることがあります。VS Code ならコマンドパレットの「TypeScript: Restart TS Server」で読み直せます(この操作は今回未実行です)。判断の基準は npx tsc -p . の結果に置くと迷いません。

まとめ

  • TS7016 は「JavaScript 本体はあるが、型定義が見つからない」というお知らせ。実行時の動作とは別の話で、型を用意すれば解消する
  • 直し方は3段階:@types を入れる → declare module "名前"; の1行で通す → 使う関数の型を波かっこの中に書く
  • d.ts が効かないときは、まず npx tsc -p . --listFiles で自分の d.ts が読まれているかを確かめ、読まれていなければ include に足す
  • 一番外側に import / export があると、その d.ts はモジュール扱いになる。グローバルに宣言したいときは export {}; と declare global { } を組み合わせる
  • typeRoots に自作フォルダだけを書くと @types が対象から外れる。自作の d.ts は include で読み込むのがわかりやすい
  • exports があるライブラリは、exports の中の types 条件と moduleResolution の組み合わせで型の場所が決まる。--traceResolution で探した順番を追える
  • svg・css はワイルドカード宣言、Window・process.env・Express Request は interface の拡張で型を付けられる

参考・出典

  • TypeScript 公式 TSConfig リファレンス「typeRoots」 https://www.typescriptlang.org/tsconfig/typeRoots.html (今回開いて確認)
  • npm レジストリの typescript パッケージ(npm view typescript dist-tags で版を確認)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TypeScript 型定義ファイル(d.ts)の書き方とTS7016の直し方」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?