noEmit
Runs the compiler as a type checker only, writing no JavaScript, maps or declarations.
"noEmit": boolean // default: false- Added in TypeScript 1.5
Parameters
| Parameter | Type | Description |
|---|---|---|
$noEmitoptional | boolean= false | Do not emit compiler output files such as JavaScript source, source maps or declarations. |
$noEmitOnErroroptional | boolean= false | A different option: emit normally, but suppress all output if any error was reported. Added in TypeScript 1.4. |
Return value
booleanNothing on disk — the compiler reports diagnostics and sets its exit code, but writes no files.
Errors
Nothing extra. noEmit does not suppress type errors — tsc still reports them and exits non-zero, which is the entire point of the flag.
noEmit tells tsc to type-check and then write nothing: no .js, no .js.map, no .d.ts. It makes the compiler a linter with an exit code.
Why nearly every modern app uses it
Vite, Next.js, esbuild, swc and Babel all strip types per-file without checking them; they are fast precisely because they never build a type graph. That leaves type errors uncaught. The standard division of labour is that the bundler produces the runtime output and a separate "typecheck": "tsc --noEmit" script — run in CI and in a pre-commit hook — produces the errors. Set it in tsconfig.json so the editor, the CLI and CI agree.
What trips people up
noEmit suppresses declarations too. A library that needs .d.ts files cannot use it; reach for emitDeclarationOnly instead, which skips the JavaScript but keeps the types.
Do not confuse it with noEmitOnError, which still emits — it just throws the output away when a diagnostic was reported. And noEmit does not make checking optional: errors are still errors, and tsc still exits non-zero, which is what fails the CI job.
Example
// Checked by `tsc --noEmit`; transpiled to JS by esbuild or Vite.
interface Route {
path: string;
lazy: boolean;
}
const routes: Route[] = [
{ path: "/", lazy: false },
{ path: "/settings", lazy: true },
];
// const broken: Route = { path: "/x" };
// -> TS2741: Property 'lazy' is missing. tsc --noEmit fails the build here;
// the bundler would strip the annotation and ship it happily.
console.log(routes.filter((r) => r.lazy).map((r) => r.path));When to use it
- Adding a "typecheck": "tsc --noEmit" script for CI while Vite or esbuild builds the app.
- Type-checking a Next.js or Remix codebase where the framework owns the build.
- Gating a pre-commit hook on type errors without producing build artefacts.
- Checking a JavaScript codebase under allowJs and checkJs without ever writing output.
Frequently asked questions
How do I type-check without generating JavaScript files?
Run tsc --noEmit, or set "noEmit": true in tsconfig.json and run tsc. The compiler builds the full program, reports every diagnostic and exits non-zero on error, but writes nothing to disk. This is the standard CI step for projects where a bundler produces the actual output.
What is the difference between noEmit and noEmitOnError?
noEmit never writes output; noEmitOnError writes output normally but discards it if any error was reported. Use noEmit when another tool builds your JavaScript, and noEmitOnError when tsc is your build tool and you do not want a broken artefact to be produced.
Why are my .d.ts files missing when noEmit is on?
Because noEmit suppresses declarations as well as JavaScript and source maps. Use emitDeclarationOnly with declaration instead — it skips the .js and still writes the .d.ts. A common layout is tsconfig.json with noEmit for the editor and CI check, plus tsconfig.build.json extending it with emitDeclarationOnly for the publish step.
More examples
The bundler-plus-checker split
Vite owns transpilation and bundling; tsc owns correctness. isolatedModules is paired with noEmit here because the bundler compiles each file alone, so the checker should reject constructs that need whole-program knowledge. CI runs the check first, so a type error fails before any artefact is built.
// tsconfig.json
{
"compilerOptions": {
"noEmit": true,
"strict": true,
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"isolatedModules": true
},
"include": ["src"]
}
// package.json
{
"scripts": {
"build": "vite build",
"typecheck": "tsc --noEmit",
"ci": "npm run typecheck && npm run build"
}
}Keeping types while skipping JavaScript
noEmit suppresses every kind of output, declarations included, so the first config silently ships an untyped package. emitDeclarationOnly is the flag that means "skip the JavaScript, keep the types" — it is what a published library wants when esbuild or Rollup handles the runtime build.
// Wrong for a library: no .d.ts is produced.
{ "compilerOptions": { "noEmit": true, "declaration": true } }
// Right: types only, JavaScript from the bundler.
{
"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"rootDir": "src"
}
}
Discussion