rootDir
Sets the input root that determines the folder structure TypeScript mirrors into outDir.
"rootDir": string // default: the longest common path of all non-declaration input files- Added in TypeScript 1.5
Parameters
| Parameter | Type | Description |
|---|---|---|
$rootDiroptional | string= the longest common path of all non-declaration input files (the tsconfig directory when composite is on) | Path, relative to the tsconfig.json, of the directory that all emitted source files must live under. It is removed from the front of each output path. |
Return value
stringThe directory that is stripped from each input path before the result is re-rooted under outDir.
Errors
Error TS6059 when a file that must be emitted lies outside rootDir, because tsc will never write output above outDir and will never silently skip a file.
Changelog
- PHP 3.0When composite is enabled, rootDir defaults to the directory containing tsconfig.json instead of being inferred from the input files.
TypeScript keeps the directory structure of your inputs in the output. rootDir is the point where that structure starts: the compiler strips rootDir off the front of every input path and re-roots what is left under outDir.
The default is inferred, and that is the trap
If you do not set it, rootDir is the longest common path of all non-declaration input files. A project with only core/a.ts and core/sub/c.ts infers core/, so dist/ contains a.js and sub/c.js — no core/ folder. Add one file at the top level and the common prefix collapses to ., the whole output relayouts, and every path in your package.json main or Docker copy step is suddenly wrong. Setting "rootDir": "src" explicitly pins the layout so it cannot drift.
What it does not do
rootDir has no effect on which files are compiled — that is include, exclude and files. It only constrains where output may land. Because the compiler refuses to write above outDir and refuses to skip a file, an input outside rootDir is error TS6059 rather than a stray ../ path in dist. That error is the usual symptom of importing a sibling package's source directly instead of its built types.
Example
// Models exactly what tsc does: strip rootDir, re-root under outDir.
function emitPath(file: string, rootDir: string, outDir: string): string {
const root = rootDir === "." ? "" : rootDir.replace(/\/+$/, "") + "/";
if (!file.startsWith(root)) {
return `TS6059: '${file}' is not under rootDir '${rootDir}'`;
}
return outDir + "/" + file.slice(root.length).replace(/\.ts$/, ".js");
}
const files = ["core/a.ts", "core/sub/c.ts", "helpers.ts"];
// Inferred rootDir when only the core/ files are inputs:
console.log(files.map((f) => emitPath(f, "core", "dist")));
// Explicit rootDir "." keeps the core/ segment and accepts helpers.ts:
console.log(files.map((f) => emitPath(f, ".", "dist")));When to use it
- Pinning "rootDir": "src" so dist mirrors src and package.json "main" never breaks.
- Diagnosing TS6059 after importing a sibling monorepo package's .ts source instead of its .d.ts output.
- Setting "rootDir": "." when you want the top-level folder names preserved inside dist.
- Keeping a test folder out of the build so it stops widening the inferred root.
Frequently asked questions
Why is my src folder missing from the dist output?
Because rootDir was inferred as src, and rootDir is always stripped from the output path. TypeScript computes the longest common path of your input files when you do not set rootDir, and if everything lives under src that prefix is removed. Set "rootDir": "." if you want dist/src/..., or accept the flatter layout and point package.json at dist/index.js.
How do I fix "File is not under rootDir" (TS6059)?
Either widen rootDir so it contains the offending file, or stop including that file in the program. The error means the file needs to be emitted but its output would land outside outDir, which the compiler will not do. In a monorepo it usually means you imported another package's .ts source directly; import its built entry point and add a project reference instead.
Does rootDir change which files get compiled?
No. rootDir has no interaction with include, exclude or files. It only controls the shape of the output directory and enforces that everything requiring emit sits underneath it. To change the set of compiled files, edit include/exclude/files.
More examples
The configuration that produces TS6059
The include pattern pulls in helpers.ts at the top level, but rootDir is core. Emitting helpers.js would require writing dist/../helpers.js, above outDir, so tsc reports TS6059: 'helpers.ts' is not under 'rootDir' 'core'. The fix is either to narrow include to core/**/* or to widen rootDir to ".".
{
"compilerOptions": {
"rootDir": "core",
"outDir": "dist"
},
"include": ["*", "core/**/*"]
}Pinning the layout in a published package
src/index.ts becomes dist/index.js and dist/index.d.ts regardless of what else is added later. Without the explicit rootDir, adding src/cli/main.ts would leave the layout alone, but removing every file except src/cli/* would move the inferred root to src/cli and emit dist/main.js — breaking "main": "dist/index.js".
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}
Discussion