TS6059Command Error
Since TS 1.5

Fix TS6059: File Is Not Under 'rootDir'

Learn why TypeScript throws TS6059 when a file outside rootDir lands in the program, and how include, project references and tsc -b fix it.

error TS6059: File 'X' is not under 'rootDir' 'Y'. 'rootDir' is expected to contain all source files

What This Error Means

TS6059 means a file in your program lives outside the folder you declared as rootDir, and the compiler cannot work out where to put its output. This is a build-layout error, not a type error — nothing about your types is wrong.

The mechanism is simple arithmetic on paths. TypeScript mirrors your source tree under outDir by removing the rootDir prefix from each input path and re-rooting the remainder: with rootDir: "src" and outDir: "dist", src/orders/cart.ts becomes dist/orders/cart.js. For a file that does not start with rootDir there is no remainder to re-root, so instead of inventing a location the compiler refuses the whole build.

error TS6059: File '/app/shared/orderStatus.ts' is not under 'rootDir' '/app/src'.
              'rootDir' is expected to contain all source files.
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~
                   in the program, but outside the folder tsc mirrors

The important word is program. Your program is not just the files you listed — it is also every file those files import, transitively. A single import "../shared/orderStatus" is enough, and import type does not help: the file still gets parsed, so it still has to live somewhere in the output layout. The check runs during type checking, so tsc --noEmit reports it too, even though nothing will be written.

Common Causes

1. A Relative Import That Reaches Above src

The most common shape: a module in src imports a shared helper that sits next to src, not inside it.

// src/orders.ts
// ❌ Broken — the import pulls a file from outside rootDir into the program
import type { OrderStatus } from "../shared/orderStatus"
//                                ~~~~~~~~~~~~~~~~~~~~~~
// Error: File '/app/shared/orderStatus.ts' is not under 'rootDir' '/app/src'.
// 'rootDir' is expected to contain all source files.
 
export function isSettled(status: OrderStatus): boolean {
  return status === "paid"
}

With { "compilerOptions": { "rootDir": "src", "outDir": "dist" }, "include": ["src"] }, the fix is to tell the truth about where your sources are: both folders are part of this project, so rootDir has to be their common parent.

// ✅ Fixed — tsconfig.json: rootDir covers both source folders
{
  "compilerOptions": {
    "rootDir": ".",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src", "shared"]
}

The price is one extra level in the output: you now get dist/src/orders.js and dist/shared/orderStatus.js instead of dist/orders.js. Update whatever points at the old path (main in package.json, your start script, Docker COPY lines) in the same commit, because tsc will not warn you that the entry point moved.

2. An include Pattern Wider Than rootDir

Here no import is involved at all. The file is a root of the program because a pattern matched it, and the compiler says so in the "file is in the program because" trace it prints under the error.

// ❌ Broken — tsconfig.json: include reaches outside rootDir
{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src", "scripts"]
}
error TS6059: File '/app/scripts/seedDatabase.ts' is not under 'rootDir' '/app/src'.
'rootDir' is expected to contain all source files.
  The file is in the program because:
    Matched by include pattern 'scripts' in '/app/tsconfig.json'

Build scripts, seeders and config files usually should not be compiled into your shipped output at all, so the better fix is to take them out of this project rather than to widen rootDir.

// ✅ Fixed — tsconfig.json compiles only what ships
{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"]
}

Give scripts/ its own tsconfig.scripts.json that extends the base config with its own rootDir and outDir (or with noEmit if you only run it through tsx). Two small projects type-check faster than one wide one, and your editor still covers both.

3. A Monorepo Package Imported By Relative Path

In a workspace it is tempting to reach across into the neighbouring package's src/. Every package that does this reports TS6059, because the other package's files can never be under this package's rootDir.

// packages/web/src/cart.ts
// ❌ Broken — reaching into another package's sources
import { formatMoney } from "../../core/src/index"
//                           ~~~~~~~~~~~~~~~~~~~~
// Error: File '/app/packages/core/src/index.ts' is not under 'rootDir'
// '/app/packages/web/src'. 'rootDir' is expected to contain all source files.
 
export const cartLabel: string = formatMoney(4250)
// ✅ Fixed — packages/web/tsconfig.json declares the dependency
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "rootDir": "src",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"],
  "references": [{ "path": "../core" }]
}
// packages/web/src/cart.ts
// ✅ Fixed — import the package, not the file
import { formatMoney } from "@acme/core"
 
export const cartLabel: string = formatMoney(4250)

Build it with tsc -b packages/web: build mode compiles packages/core first and resolves @acme/core to its emitted dist/index.d.ts, so the other package's sources never enter your program and there is nothing left outside rootDir. This is also the layout Nx, Turborepo and tsc -b expect, which is why the relative-path shortcut tends to break the tooling long before it breaks the type check.

4. composite: true Setting rootDir For You

A config with no rootDir at all can still produce TS6059, and this one catches people in generated monorepo setups. composite: true makes rootDir default to the folder containing the tsconfig.json, instead of the usual "longest common path of all inputs".

// ❌ Broken — apps/api/tsconfig.json, no rootDir in sight
{
  "compilerOptions": {
    "composite": true,
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"]
}
// apps/api/src/server.ts
import { logEvent } from "../../../libs/logging/logger"
//                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Error TS6059: File '/app/libs/logging/logger.ts' is not under 'rootDir'
// '/app/apps/api'.
// Error TS6307: File '/app/libs/logging/logger.ts' is not listed within the
// file list of project '/app/apps/api/tsconfig.json'.
 
export function startServer(): void {
  logEvent("server.start")
}

The TS6307 next to it is the same mistake seen from the file-list side. Making libs/logging a referenced composite project and importing it by name fixes both at once, exactly as in cause 3. If the library genuinely belongs to this project, state rootDir explicitly instead of letting composite guess:

// ✅ Fixed — apps/api/tsconfig.json says which tree it owns
{
  "compilerOptions": {
    "composite": true,
    "rootDir": "../..",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src", "../../libs/logging"]
}

Check what that does to your output before you commit it: with rootDir at the repository root, the mirrored tree becomes dist/apps/api/src/server.js and dist/libs/logging/logger.js. A project reference keeps the output flat, which is why it is the better fix whenever the other folder is a unit of its own.

How to Fix It

  1. Read the path in {0} and decide whether that file belongs to this project. The message names the offending file first and rootDir second; everything below follows from that one judgement. Run tsc --listFiles or read the "The file is in the program because" trace to find out whether it arrived via an import or an include pattern.

  2. Narrow include/exclude when the file should not be compiled. Tests, seeders, scripts/, old backups — if it does not ship, it does not belong in the program. Give it a sibling config (tsconfig.scripts.json) that extends the base and sets its own rootDir/outDir.

  3. Use project references for code that belongs to another package. Set composite: true on the dependency, add "references": [{ "path": "../core" }] to the consumer, import by package name rather than by relative path, and build with tsc -b. This is the only fix that scales in a monorepo, and it clears the TS6307 and TS6305 errors that travel with it.

  4. Widen or drop rootDir when the file really is yours. Set rootDir to the common parent, or delete the option entirely and let TypeScript infer the longest common path of all inputs. Both reshape outDir, so update your entry points in the same change — and do not reach for rootDirs, which merges parallel virtual trees for resolution and does nothing for this error.

  5. Keep rootDir set once it works. It is the only thing that turns "someone imported a file from outside the source tree" into an immediate build failure instead of a surprise in your published package. Deleting it to silence TS6059 also deletes the guardrail; exclude: ["dist"] plus an explicit rootDir is what keeps the next stray import honest.

FAQ

What causes TypeScript error TS6059?

TypeScript derives each output path by stripping rootDir from the input path and re-rooting the rest under outDir. When a file in the program does not sit under rootDir, that subtraction has no answer, so the compiler reports TS6059 instead of guessing a location. The file is usually there by accident — a relative import reaching above src, or an include entry such as "scripts" that is wider than rootDir. Because it is a check on the program's file set rather than on emit, tsc --noEmit reports it as well.

How do I import a file from outside src without TS6059?

Start by asking who owns the file. If it is part of this project, widen rootDir to the folder that contains both it and src (or delete rootDir and let the compiler infer it) and accept the extra level in dist:

{
  "compilerOptions": { "rootDir": ".", "outDir": "dist" },
  "include": ["src", "shared"]
}

If it belongs to another package, do not import its sources at all. Add composite: true there, list it in references, import it by package name and build with tsc -b — the import then resolves to the generated .d.ts, which lives inside that package's own output and never touches your rootDir.

Why does importing package.json not cause TS6059 anymore?

Because TypeScript 5.3 stopped applying the rootDir check to JSON inputs. The exact config that reports File '/app/package.json' is not under 'rootDir' '/app/src' on 5.0 through 5.2 compiles without a word on 5.3 and later, which is why so many older answers about import pkg from "../package.json" no longer reproduce. The file still joins your program and resolveJsonModule still infers its type, so check what your bundler and your Node runtime do with that import before relying on the silence. If you want the version string without pulling the manifest into the program at all, read it at runtime — createRequire(import.meta.url)("../package.json") — and keep the compiler out of it.

Related Errors

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges