erasableSyntaxOnly asks TypeScript to reject syntax that needs JavaScript code generation. It is useful when Node.js runs .ts files by removing type annotations instead of performing a full TypeScript compilation.

Node’s built-in type stripping does not type-check your program and does not read tsconfig.json for runtime transformations. The flag helps the separate tsc check catch code that Node cannot erase safely.

Node’s current TypeScript documentation recommends settings shaped like these:

{
  "compilerOptions": {
    "noEmit": true,
    "target": "ESNext",
    "module": "NodeNext",
    "rewriteRelativeImportExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true
  }
}

Run both commands in CI:

npx tsc --noEmit
node src/server.ts

The first command checks types and syntax. The second proves that the real runtime can execute the entry point.

Erasable syntax vs transformed syntax

This type annotation can disappear without changing JavaScript behavior:

function double(value: number): number {
  return value * 2;
}

After stripping types, ordinary JavaScript remains:

function double(value) {
  return value * 2;
}

An enum is different because TypeScript normally generates a runtime object:

enum Status {
  Pending,
  Complete
}

Type stripping cannot remove the enum and preserve its behavior. Use a JavaScript value plus a derived type:

const Status = {
  Pending: 'pending',
  Complete: 'complete'
} as const;

type Status = typeof Status[keyof typeof Status];

Syntax the flag helps catch

Syntax Why it is a problem Erasable alternative
enum Requires a runtime object as const object
Parameter property Generates a class field assignment Declare and assign the field
namespace with values Generates runtime structure ES modules and plain objects
Import alias with = Requires transformation Standard import syntax

Parameter property before:

class UserService {
  constructor(private repository: UserRepository) {}
}

Erasable version:

class UserService {
  private repository: UserRepository;

  constructor(repository: UserRepository) {
    this.repository = repository;
  }
}

The type annotation disappears, while the field assignment is already ordinary JavaScript.

Use type-only imports

When an import is used only as a type, mark it explicitly:

import type { User } from './user.ts';
import { saveUser, type SaveOptions } from './save-user.ts';

Without type, Node can treat the import as a runtime value import and fail because interfaces and type aliases do not exist at runtime. verbatimModuleSyntax helps TypeScript enforce this distinction.

Node does not apply path aliases

This can pass editor checks:

{
  "compilerOptions": {
    "paths": {
      "@app/*": ["./src/*"]
    }
  }
}

But Node’s lightweight type stripping intentionally ignores tsconfig path transformations. Use relative imports or Node package subpath imports beginning with #.

{
  "imports": {
    "#app/*": "./src/*"
  }
}

When to use erasableSyntaxOnly

Use it when:

  • Node directly executes project scripts or a small service written in TypeScript.
  • Your runtime strips types but does not transform TypeScript-only runtime syntax.
  • You want the compiler to enforce a portable subset of TypeScript.
  • Fast startup without a separate emit step is useful.

Do not adopt direct execution only because it removes one build command. A bundler or compiler is still the better fit when you need downlevel JavaScript, decorators, path rewriting, JSX transforms, bundling, or package output.

What the flag does not do

erasableSyntaxOnly does not:

  • Type-check code at runtime.
  • Make paths aliases work in Node.
  • Convert newer JavaScript for an older runtime.
  • Bundle files or copy assets.
  • Allow Node to execute TypeScript inside node_modules.
  • Replace tests for the actual production command.

Migration checklist

  1. Add erasableSyntaxOnly and run tsc --noEmit.
  2. Replace enums and parameter properties one category at a time.
  3. Mark type-only imports with type.
  4. Replace TypeScript-only aliases with runtime-supported imports.
  5. Run the real .ts entry point under the production Node version.
  6. Test scripts, workers, and test setup files, not only the web server.

The mental model is simple: if removing TypeScript syntax would require inventing new JavaScript, type stripping alone cannot run it.

Official references