JavaScript Obfuscator
Obfuscate JavaScript to deter casual reading — not encryption — entirely on this device.
Larger, slower output — more reverse-engineering friction. Moves string literals into a lookup array.
Obfuscation makes code harder to read — it is not encryption or a security control. Anyone who can run the code can still recover what it does.
Press ? for shortcuts
Do it offline:$ npx javascript-obfuscator input.js --output out.js
How to use it
- Paste JavaScript, or drop a .js file — dropped files are read locally and never uploaded, and anything past 1 MB is declined with the CLI command to run instead.
- Choose options: Compact collapses the output to one line, String array pulls string literals into a lookup table, and Control-flow flattening adds the heaviest, most disorienting structure.
- Press Obfuscate, or just watch it run as you type, and read the size report for input bytes, output bytes and the growth ratio.
- Copy the result, download it as a .js file, or share the exact tool state as a permalink; a syntax error is shown verbatim with a caret and a Jump to error button.
Paste JavaScript and get back a deliberately unreadable version. Names are rewritten to hex, and three toggles — compact output, string-array extraction and control-flow flattening — trade size and speed for reverse-engineering friction. This is a deterrent, not encryption: the running code still reveals what it does. The transform runs in a Web Worker, so the source you paste never leaves your browser, and a size report shows how much it grew.
Examples
function add(a,b){return a+b;}function add(_0x258137,_0x480b9c){return _0x258137+_0x480b9c;}Default options touch names and layout only: parameters a and b become fixed-width hex identifiers. A 30-byte source returns 62 bytes, a 2.07× growth — no strings here, so the string-array toggle would add nothing.
function classify(n){if(n>0){return "pos";}return "zero";}function classify(_0x1d4d65){var _0x56573f={'aUEYR':function(_0xdc831a,_0x4c0ed9){return _0xdc831a>_0x4c0ed9;},'HaVCb':'pos','IeLNE':'zero'};if(_0x56573f['aUEYR'](_0x1d4d65,0x0))return _0x56573f['HaVCb'];return _0x56573f['IeLNE'];}Control-flow flattening routes the comparison and both return values through a lookup object, so 58 bytes expand to 231 (3.98×) and the heavy-options note fires. The logic is unchanged, only harder to follow.
const greet = (name) => "Hello, " + name + "!";function _0x6586(_0x42cc43,_0x1324b8){_0x42cc43=_0x42cc43-0x1a2;const _0xdca7e1=_0xdca7();let _0x6586de=_0xdca7e1[_0x42cc43];return _0x6586de;}const _0x18b7b2=_0x6586;(function(_0x583388,_0x2939f1){const _0x5e0161=_0x6586,_0x51b8ce=_0x583388();while(!![]){try{const _0x59d74d=parseInt(_0x5e0161(0x1a9))/0x1+-parseInt(_0x5e0161(0x1aa))/0x2*(parseInt(_0x5e0161(0x1a5))/0x3)+parseInt(_0x5e0161(0x1a7))/0x4+parseInt(_0x5e0161(0x1a6))/0x5+parseInt(_0x5e0161(0x1a8))/0x6+-parseInt(_0x5e0161(0x1a2))/0x7+-parseInt(_0x5e0161(0x1a3))/0x8;if(_0x59d74d===_0x2939f1)break;else _0x51b8ce['push'](_0x51b8ce['shift']());}catch(_0x40b0b7){_0x51b8ce['push'](_0x51b8ce['shift']());}}}(_0xdca7,0x256c2));function _0xdca7(){const _0x21482d=['1132684fyEjqJ','2345632CTcYpo','Hello,\x20','25566KbwuKf','768430BCwGlS','1110260tcjzpo','1611360FrHfvW','44839tZVgvi','32YQMlhI'];_0xdca7=function(){return _0x21482d;};return _0xdca7();}const greet=_0x56bb2c=>_0x18b7b2(0x1a4)+_0x56bb2c+'!';With String array on, the greeting literal moves into a shuffled array reached through a decoder guarded by a rotation loop. That scaffolding turns 47 bytes into 963 (20.49×) — worth it only on code carrying many strings.
const sum = a + ;ERROR at line 1: Unexpected token (1:16)This pair is a rejection, not output. Invalid JavaScript is refused and the parser's message prints verbatim, with a caret under the offending token — here the missing operand after the plus. Fix the syntax and run again.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Obfuscation is not encryption
Everything needed to run the code ships with it, so a reader with devtools and a deobfuscator can recover the behaviour. Treat the result as friction, never a store for keys, tokens or secret logic.
The output is meant to grow
Renamed identifiers, hex numbers, and any string-array or control-flow scaffolding all add bytes. The report card prints input bytes, output bytes and the ratio — the mirror image of a minifier's savings line.
Same input, same output
The random seed is pinned, so a given source and option set always produce byte-identical output. Results stay diffable and a permalink reproduces exactly what you saw — the package's default random seed would change on every run.
JavaScript only — compile TypeScript and JSX first
The parser reads standard JavaScript. A TypeScript annotation or a JSX element stops it with a parse error, because neither is valid JS alone. Send the source through the TypeScript to JavaScript converter first, then obfuscate the plain output.
Self-defending and debug-protection stay off
The package can weave in anti-tampering and anti-debugger traps, but both routinely break working code once devtools open. This tool leaves them off and does not expose them.
Already-minified or obfuscated code grows again
Output from a minifier, or from this tool, is still valid JavaScript, so a second pass re-mangles names and re-adds scaffolding. It comes back the same size or larger — obfuscation never shrinks what you feed it.
Common errors
ERROR at line 1: Unexpected token (1:10)- A JSX element is not valid JavaScript on its own, so the parser stops at the opening angle bracket in column 11 (babel reports the zero-based 1:10). Compile the JSX first, then obfuscate the emitted JavaScript.
Input is larger than the 1 MB limit for this tool. For bigger files use the CLI: npx javascript-obfuscator input.js --output out.js- The tool declines an oversized paste rather than obfuscating a slice, and your text stays in the editor. The suggested command runs the same obfuscator on your machine with no size ceiling.
ERROR at line 1: Unexpected token (1:7)- The type annotation after the colon is TypeScript, not JavaScript, so the parser stops at column 8. Strip or compile the types first; every option then behaves the same on the resulting plain JavaScript.
FAQ
Can obfuscated JavaScript be reversed, and is it secure?
No — obfuscation is a deterrent, not a security control. The code still runs in the browser, so its logic can be traced with devtools and largely restored by deobfuscators; original names and comments are gone, but behaviour is always recoverable. Keep true secrets on a server.
Why is the output bigger than my source?
Because obfuscation adds structure: identifiers grow into hex names, numbers become hex, and the string-array and control-flow options insert decoder and dispatcher scaffolding. The report card shows the input-to-output ratio, so the cost is never hidden.
Why do I get the same output every time?
The random seed is fixed, making obfuscation a pure function of your input and options. Identical input yields byte-identical output, so a diff in review is meaningful and a permalink reproduces the result exactly — never a per-run random seed.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Obfuscate the current buffer again, options included.
- ⌘/Ctrl + S
- Download the obfuscated source as a .js file, past the browser's save shortcut.
- ⌘/Ctrl + C
- With nothing selected, copy the whole obfuscated output; a selection copies normally.
- ⌘/Ctrl + Shift + C
- Copy the entire obfuscated output even with text selected.
- t
- Switch light and dark theme, unless the editor has focus.
- ? or ⌘/Ctrl + /
- Open the shortcut help for this obfuscator.
- Esc
- Dismiss the open dialog; your input is never cleared.