TypeScript

You do not obfuscate TypeScript. You obfuscate what it emits.

TypeScript never reaches a browser or a server — it compiles away. Types are erased, and what ships is plain, readable JavaScript carrying every string, endpoint, and rule you wrote. “It’s TypeScript, it’s compiled” is one of the most expensive misconceptions in front-end security. Protect the output, and stop publishing the map that points back to your source.

The Fastest Own-Goal

app.js + app.js.map = your .ts source

Ship a source map next to a protected bundle and you have published the original TypeScript — names, structure, and with inlineSources, the literal file text.

Types eraseInterfaces vanish. They protect nothing at runtime.
Enums persistRegular enum member names ship as string literals.
Maps stay privateGenerate them, keep them, never deploy them.
Type Erasure

What survives the compiler is what an attacker reads

TypeScript’s compiler is a transpiler with a type checker attached. It validates your types, then throws them away and emits JavaScript. That is excellent for runtime performance and terrible for anyone assuming the compile step hid something. Nothing was hidden — the types were never going to be there at runtime in the first place.

1. Your TypeScript
interface License {
  key: string;
  tier: "trial" | "pro";
}

function check(l: License): boolean {
  return l.tier === "pro" &&
         l.key !== "TRIAL";
}
2. What tsc emits
function check(l) {
  return l.tier === "pro" &&
         l.key !== "TRIAL";
}
3. After protection
function _0x8c(_0x1f){
  return _0x1f[_$_b2[0x0]]===
  _$_b2[0x1]&&_0x1f[_$_b2[0x2]]
  !==_$_b2[0x3];}

The interface vanished and bought you nothing at runtime. The logic and the strings "pro" and "TRIAL" shipped exactly as written — searchable in any bundle. Only the third column stops answering questions.

Compile, protect, then deploy
npx tsc --noEmit           # type check
npm run build              # bundle + minify -> dist/
npx jso-protector \
  --preset maximum \
  --input dist \
  --output dist-protected

# keep maps OUT of the deploy
find dist-protected -name '*.map' -delete
The Order of Operations

Type check → build → protect → strip maps

  • Never protect .ts files. The compiler must read them as written. Protection consumes emitted JavaScript — the same rule that applies to Angular and Vue.
  • Let your bundler minify first, then protect the bundle. See minification vs obfuscation for why one is not the other.
  • Archive source maps, do not deploy them. They are your symbolication key and, if published, your source.
  • Exclude declaration output from application deploys. A .d.ts is a precise map of your API surface.
  • Smoke test the protected build before it ships — especially anything reading keys off an API response.
TypeScript Specifics

Three constructs that behave differently than people expect

Enums leave their names behind

A regular enum Role { Admin, Trial } emits a real object with "Admin" and "Trial" as string keys, plus a reverse mapping. Those literals are searchable. const enum inlines instead and leaves nothing — prefer it when member names are sensitive.

Decorator metadata re-adds types

Interfaces erase — unless emitDecoratorMetadata is on. Then the compiler deliberately writes type names into the output so reflection can read them. Useful for DI; worth knowing it re-introduces exactly what erasure removed.

.d.ts is a published API map

Perfect for a library your consumers type against. For a deployed application it is disclosure with no upside — every exported signature, spelled out. Keep declarations out of the deploy artifact.

Source Maps

The one setting that quietly undoes all of this

Protection and source maps are exact opposites: one removes the mapping from shipped code back to intent, the other restores it perfectly. Both are useful. Only one belongs on your web server.

What a map gives away

Original file names, original identifier names, and original line structure. With inlineSources: true it embeds your .ts text outright — the map alone reconstitutes the project.

Why they still get published

Default bundler configs emit them, and a //# sourceMappingURL= comment survives into the deploy folder. Nobody decides to publish source maps; they just do not decide to remove them.

The right pattern

Generate maps, store them in CI artifacts alongside the protection run’s identifier map, and delete them from the deploy. Then use symbolication to read production traces internally.

Frequently Asked

TypeScript protection questions, answered

Can I obfuscate .ts files?

No. TypeScript never runs. Obfuscating source only breaks tsc. Protect the JavaScript it emits — that is the artifact that ships.

Doesn’t compiling protect my code?

No. It is transpilation, not compilation to a machine format. The output is ordinary readable JavaScript. Types are a correctness tool, not a security boundary.

Are my source maps a problem?

If they are deployed, yes — a serious one. They map protected output straight back to original names and often the original text. Keep them private.

Do my enum names leak?

Regular enums, yes — member names ship as string keys. const enum inlines and leaves nothing behind.

What must I reserve?

The same string-matched contracts as any JavaScript app: keys read off API responses, string DI tokens, route paths, and anything persisted to storage across releases.

How do I debug protected builds?

Keep the identifier map from each protected build, privately, and symbolicate field stack traces back to readable names.

Start Now

Check your deploy folder for .map files

Before anything else, look at what you are already serving. If there is a .js.map next to your bundle, your TypeScript is public today — and that is a one-line fix. Then take the emitted bundle and run it through the obfuscator to see the difference.