Run TypeScript Natively in Node.js: Type Stripping

Photo by Markus Spiske on Pexels
Yes. Since Node 22.6.0 (behind --experimental-strip-types) and by default from Node 23.6.0 and 22.18.0, Node runs .ts files directly using type stripping. It erases the type annotations and executes the remaining JavaScript, so you no longer need tsc, ts-node, or a bundler just to run a file.
No. Type stripping only deletes type annotations and replaces them with whitespace; no type checking happens. Node will run code full of type errors without complaint. You still need to run npx tsc --noEmit separately, typically as a CI stage, to catch type errors.
Type stripping only handles erasable syntax that leaves valid JavaScript once removed. Enums, namespaces with runtime code, parameter properties, and import aliases must be replaced by new JavaScript rather than deleted, so plain stripping throws ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. Use --experimental-transform-types or refactor them into erasable forms.
--experimental-strip-types (the default) only deletes type annotations, keeping character positions so stack traces stay accurate. --experimental-transform-types actually rewrites non-erasable features like enums and namespaces into JavaScript. Transform mode shifts positions, so pair it with --enable-source-maps.
Node uses a package called amaro, which wraps the WebAssembly build of swc, the Rust-based TypeScript and JavaScript toolchain. swc does the fast parse-and-strip work, while amaro is the seam Node uses to call it and to upgrade the bundled TypeScript version independently of Node.

Photo by Markus Spiske on Pexels
Key Takeaway
Type stripping lets Node.js run TypeScript files directly by erasing type annotations and replacing them with whitespace, so no tsc build step is needed. It performs no type checking. It arrived behind --experimental-strip-types in Node 22.6 and runs unflagged from Node 23.6. Enums and namespaces need transform mode.
For years the honest answer to how you run TypeScript on the server was: you do not, not really. You run JavaScript that a build step produced from TypeScript. Every project carried a tsc invocation, or ts-node, or a bundler, sitting between the file you wrote and the process that actually executed. That extra hop is exactly what Node.js has now removed for a large class of code.
The mechanism is called type stripping, and the mental model is simpler than any transpiler. Node does not understand your types, compile them, or reason about them. It deletes them. What is left is plain JavaScript, and that is what the V8 engine runs. Once you internalise that one sentence, every capability and every limitation of the feature follows from it.
When Node encounters a .ts file, it hands the source to an internal transform that removes erasable TypeScript syntax and puts whitespace in its place. A line like const port: number = 3000 becomes const port = 3000. The colon and the type annotation are gone; the character positions are preserved. That whitespace trick matters: because nothing shifts, stack traces still point at the correct line and column in your original file without needing a source map.
Crucially, no type checking happens. Node strips the annotations and moves on. If you assigned a string to that number-typed variable, Node will not complain, because by the time it runs the code the type information no longer exists. Type stripping gives you the run step for free but not the safety net. The commands below show the flag history and the compiler call you still need in CI.
# Node 22.6 through 22.17: type stripping is behind a flag
node --experimental-strip-types app.ts
# Node 22.18 / 23.6 and later: no flag, just run the file
node app.ts
# Opt back out if some tool needs the old behavior
node --no-experimental-strip-types app.ts
# Node does NOT type check — run the compiler yourself in CI
npx tsc --noEmitThis landed in stages, and knowing which version does what saves confusion when a script that runs on your laptop fails on an older CI image:
This is the single most important thing to tell your team before they lean on it. Running node app.ts is not the same as running tsc. Node will happily execute code riddled with type errors, because it never looked at the types in the first place. Type stripping replaces the transpile step, not the verification step. Your editor still checks types as you write, but the runtime does not, and neither does your pipeline unless you make it.
Keep a real type check in CI. Type stripping removes the build, not the compiler. Run npx tsc --noEmit as its own pipeline stage so a type error fails the build before it ships. The recommended tsconfig for this setup uses noEmit true with target esnext and module nodenext, since tsc now only validates and Node does the running.
Type stripping only works on syntax that can be deleted and leave valid JavaScript behind. Type annotations, interfaces, type aliases, and import type statements are all erasable: take them out and the remaining code stands on its own. Some TypeScript features are different. An enum, a namespace containing runtime code, parameter properties in a constructor, and import aliases all need to be replaced by new JavaScript, not merely removed. Plain type stripping cannot do that, so Node throws ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
// Erasable syntax — runs under plain type stripping
const port: number = 3000
interface User { id: string; name: string }
import type { IncomingMessage } from "node:http"
// Non-erasable syntax — throws ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
enum Role { Admin, User }
namespace Api { export const version = 1 }
class Service { constructor(private readonly db: DB) {} }
// Run non-erasable code by asking Node to transform it instead:
node --experimental-transform-types app.ts
// Or lock your codebase to strip-safe syntax in tsconfig.json:
// "erasableSyntaxOnly": trueIf your codebase depends on enums or namespaces with runtime code, plain node file.ts will throw. Either run it with --experimental-transform-types, refactor those constructs into erasable equivalents such as a plain object with as const, or set erasableSyntaxOnly true in tsconfig so the compiler flags non-erasable syntax at author time rather than letting it fail at runtime.
When you genuinely need those non-erasable features, --experimental-transform-types switches Node from deleting types to actually transforming them into equivalent JavaScript. It handles enums, namespaces with runtime code, and similar constructs that plain stripping rejects. Because transform mode rewrites code rather than blanking it out, character positions move, so pair it with --enable-source-maps to keep stack traces honest. It is heavier than stripping, which is why the lightweight strip-only path is the default.
Node does not ship a TypeScript parser of its own. The internal loader is a package called amaro, which wraps the WebAssembly build of swc, the Rust-based TypeScript and JavaScript toolchain. swc does the fast parse-and-strip work; amaro is the thin seam Node uses to call it and to let the bundled TypeScript version be upgraded independently of Node itself. That Rust core is why stripping adds so little startup overhead compared with a JavaScript-based transpiler.
| Aspect | Node type stripping | ts-node or tsc build |
|---|---|---|
| Type checking | None — annotations erased, never verified | Full type checking via the compiler |
| Build step | None — run the .ts file directly | A transpile step or long-lived runner |
| Startup overhead | Near zero — Rust-based swc via amaro | Slower — JS transpile or a build stage |
| Enums and namespaces | Rejected unless transform mode is on | Supported out of the box |
| Best fit | Scripts, small services, the dev loop | Large apps using the full TypeScript feature set |
My rule of thumb is straightforward. For scripts, tooling, and services written in erasable syntax, I run them with node directly and let a separate tsc --noEmit stage catch type errors in CI. For codebases leaning on enums, namespaces, or decorators, I either reach for transform mode or keep a real build. Either way, the days of a mandatory build step just to execute a .ts file on the server are over, and that is a genuinely nice thing to stop thinking about.
Sources