tsx
tsx runs TypeScript files in Node.js directly: tsx ./file.ts works like node ./file.ts but strips and transforms the types on the fly with esbuild. It handles both ESM and CommonJS without you thinking about it, respects tsconfig.json (including paths), ships a watch mode that reruns your script when imports change, and exposes hooks (node --import tsx, tsImport) so the same transform can power test runners and scripts. It deliberately does zero type checking; it is an execution tool, not a compiler replacement.
The current default answer to 'how do I just run this TypeScript file', because it removed the loader and module-format ceremony that made ts-node painful. Keep tsc --noEmit in CI since tsx checks nothing, and reconsider once Node's native type stripping covers your codebase.
Use it if
- You want npm run dev to just execute src/server.ts with restart-on-change: tsx watch is the whole setup
- You keep hitting ESM/CJS friction with ts-node: tsx's main selling point is that mixed module formats, .mts/.cts files, and tsconfig paths work without loader configuration
- You write one-off TypeScript scripts or scratch files and want them runnable with a shebang or npx tsx, no build step
- You need to load user-provided TS files at runtime from a JS tool: tsImport from tsx/esm/api scopes TS support to specific imports without changing the whole runtime
- You expect type errors to stop execution: esbuild strips types without checking them, so broken types run fine under tsx and you must run tsc --noEmit separately or ship bugs
- Your code relies on emitDecoratorMetadata (TypeORM, NestJS-style DI): esbuild does not emit decorator metadata, and tsx inherits that; ts-node using the real TypeScript compiler handles it
- You are on recent Node and your TS is mostly type annotations: Node's built-in type stripping runs such files natively now, and a dependency plus its esbuild binary buys you little
- You are looking for a production runtime: transforming on every boot adds startup cost and another moving part; build once with esbuild or tsc and run plain node in production
- You need tight control of the exact JS emitted (targets, helpers, const enum behavior): tsx exposes almost no transform configuration on purpose
Setup reality
npm install -D tsx and npx tsx file.ts works immediately; there is no config file at all, which is the point. The install pulls esbuild, which lands as a platform-specific binary via optionalDependencies, occasionally annoying in pnpm setups, offline mirrors, or when CI caches a lockfile across OSes. Node 18+ is required, and the node --import tsx flag replaced the deprecated --loader form used on Node 20.5.1 and below, so copy-pasted older instructions mislead. Remember what is missing by design: no type checking (pair it with tsc --noEmit), no decorator metadata, and watch mode ignores node_modules, dist, and hidden directories unless you pass --include.
Patterns
Run a TypeScript file directlyrun-ts-file
npx tsx ./src/script.ts
# or installed globally
tsx ./src/script.tsWorks for ESM and CJS, .ts/.tsx/.mts/.cts, with tsconfig paths resolved. No types are checked; a file with type errors still runs.
Rerun on change with watch modewatch-mode
tsx watch ./src/server.ts
# watch extra non-imported files, keep the screen
tsx watch --include "./config/*" --clear-screen=false ./src/server.tsWatches all imported files but ignores node_modules, dist, vendor, and hidden directories. Press Return in the terminal to force a rerun.
Wire it into package.jsondev-script
{
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "node dist/server.js",
"typecheck": "tsc --noEmit"
}
}Keep start pointing at built output; tsx is a dev runner. The typecheck script is not optional in spirit, it is the type safety tsx deliberately skips.
Use the node CLI with tsx as a hooknode-import-flag
node --import tsx ./file.ts
# Node 20.5.1 and below used the deprecated loader API
node --loader tsx ./file.tsUseful when you need node's own flags (--inspect, --max-old-space-size) alongside TS support. The --loader form is deprecated; prefer --import on modern Node.
Add TS support to another CLI via NODE_OPTIONSenhance-other-binaries
NODE_OPTIONS='--import tsx' npx some-binary ./config.tsThis is how you make third-party Node CLIs read TypeScript config or plugin files without the tool supporting TS itself.
Register for only one module systemcjs-or-esm-only
# CommonJS files only
node --require tsx/cjs ./file.ts
# ESM files only
node --import tsx/esm ./file.tsThe split registrations exist for tools that already handle one side; plain --import tsx covers both and is what you want in most cases.
Load one TS file from JavaScript at runtimets-import-scoped
import { tsImport } from 'tsx/esm/api';
const loaded = await tsImport('./user-config.ts', import.meta.url);
// with options
await tsImport('./plugin.ts', {
parentURL: import.meta.url,
tsconfig: './custom-tsconfig.json'
});The second argument (parent URL) is required to resolve relative paths. tsImport does not cache: importing the same file twice loads it twice, by design.
Enable TS loading from inside your entry fileentry-point-registration
import 'tsx';
// dynamic imports after this line can load TS
const mod = await import('./tasks.ts');Only dynamic imports after the registration work; static imports were already resolved. The docs themselves warn this hides the magic from collaborators, so prefer the CLI flag.
Make a TypeScript file executableshebang-script
#!/usr/bin/env tsx
console.log('argv:', process.argv.slice(2));
// chmod +x ./scripts/deploy.ts && ./scripts/deploy.ts stagingRequires tsx available on PATH (global install or running inside npm scripts where node_modules/.bin is added).
Point tsx at a different tsconfigcustom-tsconfig-path
TSX_TSCONFIG_PATH=./tsconfig.scripts.json tsx ./scripts/migrate.tsBy default tsx looks up the nearest tsconfig.json; the env var is the only override, there is no CLI flag for it.
Pair tsx with real type checkingtypecheck-in-ci
# CI pipeline
npx tsc --noEmit
npx tsx ./scripts/integration-test.tstsx strips types with esbuild and never diagnoses them. Without a tsc --noEmit gate, type errors reach main and only surface as runtime behavior.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| ts-node | npm | You need real TypeScript compiler semantics at runtime: emitDecoratorMetadata, optional type checking, or compiler plugins. |
| jiti | npm | You are a library author needing to require a user's TS config file synchronously inside your tool, rather than running whole apps. |
| esbuild-register | npm | You only need a CJS require hook with esbuild transforms and want the smallest possible layer over esbuild itself. |