Obfuscation raises the price of reading your code. It is not a lock.
Anything the browser runs, the browser can show. Obfuscation changes how long that takes and how much patience the reader needs. A student poking at your pricing widget gives up in ten minutes. Someone paid to break your licence check still gets there, with a debugger and an afternoon.
That split matters when you decide what to put in the file at all.
What the cost increase covers
- Casual source reading by a curious user with view-source open.
- Copying a scoring formula or pricing rule straight out of your bundle.
- Deleting a licence branch by searching for the words in the error message.
- Scrapers that pull endpoint paths out of thousands of sites by pattern match.
What stays exposed either way
- Every network request your code makes, sitting in the Network tab with headers.
- API keys inside the file. A breakpoint on the fetch call prints them decoded.
- Runtime behaviour. Values pass through the console the moment a reader sets a watch.
- Business logic worth real money, given a reader with the right tooling.
Rule of thumb: any secret that must stay secret belongs behind a server endpoint. Obfuscate the client to slow readers down, never to store something you cannot afford to lose.
What each transform costs you
The defaults on this page favour a small file. Turn things on deliberately, because the expensive transforms are also the ones users feel.
| Transform | Effect on the file | Size | Runtime | Effort to reverse |
|---|---|---|---|---|
| Compact output | Removes whitespace and newlines | Smaller | None | Seconds |
| String array | Literals move into one indexed array | Slight growth | Negligible | Scriptable |
| Base64 encoding | Array entries stop reading as text | Around 33 percent on strings | Small, cached | Scriptable |
| RC4 encoding | Array entries decrypt per lookup | Similar to base64 | Felt in hot loops | Scriptable with effort |
| Control flow flattening | Blocks become a switch dispatcher | Up to double | Around 1.5x slower | Hours by hand |
| Dead code injection | Unreachable branches interleave with yours | Up to 200 percent | Parse time only | Detectable by tooling |
| Self defending | Formatting the output breaks it | Small | None | Blocks the lazy route |
| Debug protection | DevTools triggers a stall loop | Small | Timer always running | Blocks the lazy route |
Read the reverse effort column next to the size column before turning everything on. Compact output and the string array give most of the practical benefit for a fraction of the weight. Control flow flattening is where a 40 KB widget turns into 90 KB.
Picking a profile that matches what you are guarding
- Light, for embeds and widgets
- A chat bubble or analytics snippet loading on customer sites. You still need to read stack traces from bug reports, and every kilobyte lands on someone else's page budget. Renaming plus a string array is the ceiling here.
- Balanced, for licence gates and paid features
- The code checks a token and unlocks a feature. Base64 strings stop a text search for the flag name, and partial flattening makes the branch tedious to trace. Growth near 2x is acceptable on a file loaded once behind a login.
- Hardened, for algorithms you sell
- A pricing engine, a game client, a scoring model shipped to browsers. Take the 4x growth and the slower loops on purpose. Test on a mid-range phone before you accept it, since flattening plus RC4 shows up on weaker CPUs first.
One function, before and after
Light settings on a short check. Note what survives: the shape of the call is gone, the strings are gone from plain view, the network path is not.
function hasLicence(user) {if (user.plan === "pro") {return true;}
console.log("upgrade required");return false;}function _0x3f1a(_0x4c2e){var _0x1b=_0x2d7f;if(_0x4c2e[_0x1b(0x1a2)]===_0x1b(0x1a3))return!![];console[_0x1b(0x1a4)](_0x1b(0x1a5));return![];}The identifiers are gone for good. The behaviour is identical, including the request that follows. A reader who sets a breakpoint on the return still learns the answer in one step, which is the honest limit of every client side check.
Checks to run before the output ships
- Parse the result. The button above compiles the output without executing it. A failure here means a transform hit syntax the obfuscator mishandled, usually decorators or unusual class fields.
- Click every path in a real page. Parsing proves nothing about behaviour. Code reading
fn.name, comparingFunction.prototype.toStringoutput, or resolving a handler by string name breaks under renaming and stays quiet until a user hits it. - Never obfuscate twice. Running output back through the tool compounds the size and occasionally produces code the engine rejects.
- Do not minify afterwards. Minify first, obfuscate second. Self defending exists to break exactly that reordering.
- Keep the original in version control. Obfuscated output is not editable. Your next patch starts from the source, always.
- Decide about source maps. Shipping one alongside obfuscated code hands back everything you removed. Generate maps for your error tracker, keep them off the public path.
Where obfuscating is the wrong call
- Node and backend code. Nobody downloads your server file. You pay the slowdown for no reduction in exposure.
- Anything on a render loop. Canvas draws, scroll handlers, animation frames. Control flow flattening inside a 60 frames per second loop is felt on the first slow device.
- Packages other developers debug. An obfuscated npm dependency turns every downstream bug report into a dead end. Publish readable, ship obfuscated only in your own bundle.
- GPL and similar licences. Distributing obfuscated output while calling it the source form fails the licence condition. Read the terms before you build.
- Code you need clean stack traces from. Error monitoring against obfuscated frames without a stored source map gives you line numbers pointing at nothing.
Your source stays in this tab
The obfuscator library loads into the page and runs on your machine. No request carries the code anywhere, so proprietary logic, internal endpoints and half-finished work stay local. The text lives in the editor until you reload, so clear both panels when you finish on a shared machine.
