Guide

How to obfuscate JavaScript, one transform at a time

JavaScript obfuscation rewrites code so it still runs the same way but is much harder to read, search and reuse. This guide applies the transforms one by one to the same short function and shows the real output after each step, so you can see what each option does, what it costs in size, and what it needs from you before the protected file ships.

The Short Version

Build → obfuscate a copy → test → ship

Obfuscate the built, minified bundle, never your source tree. Keep the original and its source maps private, and run your tests against the protected copy before it is published.

RenameIdentifiers lose their meaning.
Hide stringsLiterals stop being searchable.
RestructureControl flow stops reading top to bottom.
What It Is

Obfuscation is not minification, and it is not encryption

A minifier removes whitespace and shortens some names to make files smaller; a formatter puts the structure straight back. Encryption hides data from anyone without the key — but a browser has to execute your code, so it always ends up with everything it needs to run it. Obfuscation sits between the two: the code stays executable, while names, strings and structure are rewritten so that understanding it takes far longer. It raises the cost of copying, tampering with and reverse-engineering your JavaScript. It does not make secrets safe to ship.

Step 0

The input: an 8-line licence check

Every step below protects this same function. It is 220 bytes, and every protected version prints the same result (true) when run.

example.js
function isLicensed(user) {
  var plan = user.plan;
  if (plan === "pro" || plan === "team") {
    return user.key !== "TRIAL-KEY";
  }
  return false;
}
console.log(isLicensed({ plan: "pro", key: "A1B2" }));

What a reader learns from it

  • The function name says exactly what it guards.
  • The plan names "pro" and "team" are searchable in the shipped file.
  • The trial key "TRIAL-KEY" is right there to copy.
  • The logic reads top to bottom in one pass.

Each transform below removes one of those answers.

Step 1

Rename identifiers (Replace Names, Replace Globals)

Replace Names rewrites local variables and parameters with short generated names. Top-level names are left alone, because other scripts may call them by name; Replace Globals renames those too, consistently across a project. Formatted here for reading — real output is usually a single line.

Replace Names + Short Local Name · 182 bytes
function isLicensed(b)
{
	var a=b.plan;
	if(a=== "pro"|| a=== "team")
	{
		return b.key!== "TRIAL-KEY"
	}
	return false
}
+ Replace Globals · 164 bytes
function a(c)
{
	var b=c.plan;
	if(b=== "pro"|| b=== "team")
	{
		return c.key!== "TRIAL-KEY"
	}
	return false
}
console.log(a({plan:"pro",key:"A1B2"}))

Renaming makes the file smaller and costs nothing at run time. Its one requirement: any name that code outside this file calls — a public API, a framework callback, a property read off JSON — must be kept, with a variable exclusion rule. The strings, though, are untouched.

Step 2

Move and encode strings (Move Strings, Encode Strings)

Move Strings lifts every literal into one generated table and replaces each use with an index. Encode Strings respells the literals as escape sequences, so a text search of the file for TRIAL no longer finds anything.

Replace Names + Move Strings + Encode Strings · 317 bytes
var _$_2476=["\x70\x72\x6F","\x74\x65\x61\x6D","\x54\x52\x49\x41\x4C\x2D\x4B\x45\x59","\x41\x31\x42\x32"];
function isLicensed(b)
{
	var a=b.plan;
	if(a=== _$_2476[0x0]|| a=== _$_2476[0x1])
	{
		return b.key!== _$_2476[0x2]
	}
	return false
}

Escape sequences stop casual searching, but any JavaScript console decodes them instantly. That is what the next step is for.

Step 3

Encrypt strings (Encrypt Strings)

Encrypt Strings stores the string table in a scrambled form and rebuilds it at start-up through generated decoding logic. It works on the table Move Strings builds, so it needs Move Strings on.

+ Encrypt Strings · 788 bytes (decoder shortened here)
var _$_2476=(function(n,r)
{
	var x=n.length;
	var s=[];
	for(var j=0;j< x;j++)
	{
		s[j]= n.charAt(j)
	}
	/* ... generated shuffle and split logic ... */
	return c
}
)("m%or-Aa1%T2YAeEBKpI%LRt",5638772);
function isLicensed(z)
{
	var y=z.plan;
	if(y=== _$_2476[0x0]|| y=== _$_2476[0x1])
	{
		return z.key!== _$_2476[0x2]
	}
	return false
}

Now neither the plan names nor the trial key appear anywhere in the file, encoded or not. The cost is size — this tiny example more than doubled — and a little start-up work to rebuild the table.

Step 4

Restructure control flow (Code Transposition, Flat Transform)

Code Transposition moves top-level code into a generated wrapper so statements no longer appear in the order you wrote them. Flat Transform then breaks logic apart — here, every comparison becomes a call to a generated helper. Flat Transform needs Code Transposition (or Compressor, which turns it on), and transposition skips code shorter than 256 characters by default, so this example lowered that threshold to show it.

Rename + strings + Code Transposition + Flat Transform · 398 bytes
var a;
(function()
{
	function d(h,i)
	{
		return h!== i
	}
	function e(h,i)
	{
		return h=== i
	}
	var b=["\x70\x72\x6F","\x74\x65\x61\x6D","\x54\x52\x49\x41\x4C\x2D\x4B\x45\x59","\x41\x31\x42\x32"];
	function c(g)
	{
		var f=g.plan;
		if(e(f,b[0x0])|| e(f,b[0x1]))
		{
			return d(g.key,b[0x2])
		}
		return false
	}
	a= c;console.log(c({plan:b[0x0],key:b[0x3]}))
}
)()
Step 5

Compress and ship (Compressor)

The Compressor packs the whole result into a self-extracting wrapper. This is the Standard preset (Replace Names, Move Strings, Encode Strings, Compressor) on the same input — the first 260 of 1,956 bytes:

Standard preset · 1,956 bytes, one line
var isLicensed;(function(){var HCs='',Drg=744-733;function xPS(m){var w=1443726;var a=m.length;var u=[];for(var b=0;b<a;b++){u[b]=m.charAt(b)};for(var b=0;b<a;b++){var e=w*(b+496)+(w%33940);var l=w*(b+656)+(w%33604);var t=e%a;var k=l%a;var y=u[t];u[t]=u[k];u[k ...

The Compressor runs its payload through dynamic evaluation. If your site sends a Content-Security-Policy, it has to allow that (script-src 'unsafe-eval') or the protected file will not start — check before you enable it. The same applies to Self Defending, Self Healing and Anti Monkey Patching.

Which Options Need Which

Option dependencies, measured

We protected the same input with each of these options on alone and compared the bytes: alone, each left the output unchanged. They only take effect with one of the options in the right-hand column. The online tool switches the prerequisite on for you and reports any option that had no effect.

OptionOnly works withWhy
Flat TransformCode Transposition or CompressorIt restructures the wrapper transposition builds.
Dead Code InsertionCode Transposition or CompressorDead code is emitted into the same wrapper.
Short Local Name, Deep Obfuscation, Replace GlobalsReplace NamesThey change how the renaming pass names things.
Encrypt Strings, Move MembersMove StringsThey work on the string table Move Strings creates.
Three Ways To Run It

Online, in your build, or on your desktop

Online tool

Paste or upload a sample and pick a preset. No account; samples up to 32 KB, and every option — paid ones included — can be previewed before you buy. Best for trying settings.

Open the online obfuscator ›

npm CLI in your build

Protect the built output after your bundler runs. With no account the free options run on your own machine — nothing is uploaded and there is no size cap. Paid options run on the hosted service and need a plan.

npm install --save-dev javascriptobfuscator-com
npx javascriptobfuscator-com --preset free --input dist --output dist-protected

CLI and CI guide ›

Desktop app

Whole folders in one batch, and JavaScript embedded in HTML, PHP, ASP, ASPX or JSP protected in place. Local Standard keeps basic ES5 files entirely offline.

Download for Windows ›

Before You Ship

Test the protected copy, not the original

  • Run your test suite against the protected output. A file that protects without errors has not yet been shown to work. See the compatibility checklist.
  • Keep names other code depends on: public APIs, framework hooks, and keys read from JSON or the DOM.
  • Check your Content-Security-Policy if you use the Compressor or runtime defences.
  • Never deploy source maps next to protected files; they map the output straight back to your source.
  • Keep the identifier map from each build, privately, to decode production stack traces.
What It Cannot Do

Limits worth knowing up front

  • Secrets are still secrets. An API key in front-end code is readable however it is protected; keep it on the server.
  • Checks belong on the server. A licence or payment check that only runs in the browser can be bypassed by whoever controls the browser.
  • It raises cost, not walls. Determined analysis of a protected file is slower and harder, not impossible.

Our failure modes guide covers what breaks and why.

Frequently Asked

JavaScript obfuscation questions, answered

Can obfuscated JavaScript be reversed?

It can be analysed, because the browser has to run it. Formatters restore whitespace, but not the original names, string layout or control flow. Obfuscation raises the cost of understanding and reusing the code; it is not a security boundary.

Does obfuscation make JavaScript bigger or slower?

Renaming makes it smaller. String tables, encryption and control-flow transforms make it larger: in the example on this page an 8-line function went from 220 bytes to 164 after renaming and 788 after string encryption. Stronger settings can add runtime overhead, so measure startup and the flows that matter.

Should I minify before or after obfuscating?

Build and minify first, then obfuscate the bundle. Minifying obfuscated output can undo or break transforms that depend on the generated structure. More in minification vs obfuscation.

Why did an option not change my output?

Some transforms only work with another one on — see the dependency table above. Code Transposition also skips code shorter than 256 characters by default, so a very small sample may show no change from it.

Can I obfuscate JavaScript for free?

Yes. The online tool needs no account and previews every option on samples up to 32 KB. With no account, the npm CLI runs the free options (renaming, string encoding and moving) on your own machine: nothing is uploaded and there is no size cap. The paid transforms need a plan.

Try It

Run this example yourself

Paste the 8-line example into the online obfuscator, switch presets, and read the report under the output: it lists what was applied, which options are paid, and anything that had no effect.

JavaScript obfuscation techniques explained · Protecting JavaScript (overview) · Online code obfuscator · VM bytecode obfuscation · Obfuscation glossary