Rollup Plugin

Obfuscate JavaScript in a Rollup build

One plugin protects every JavaScript chunk Rollup writes, after minification and before the files reach disk, and deletes the source maps that would undo it. The free options run on your own machine with no account. Below: the exact config we tested, and what came out.

Tested 2026-10-29

Real build, same result

Rollup 4.66 with @rollup/plugin-terser and javascriptobfuscator-com 0.5.0, no account. The protected bundle printed exactly what the unprotected one printed.

HookgenerateBundle, after renderChunk
Files.js, .mjs and .cjs chunks
Source mapsDeleted for protected chunks
Setup

Install the package and add one plugin

The Rollup plugin ships inside the javascriptobfuscator-com package as javascriptobfuscator-com/rollup; there is nothing else to install.

Terminal
npm install --save-dev javascriptobfuscator-com @rollup/plugin-terser
rollup.config.mjs
import terser from "@rollup/plugin-terser";
import jsoProtector from "javascriptobfuscator-com/rollup";

export default {
  input: "src/main.js",
  output: { dir: "dist", format: "es", sourcemap: true },
  plugins: [terser(), jsoProtector()]
};

What the plugin does on rollup -c

  1. Waits for the finished chunks. It runs in Rollup’s generateBundle hook, after every renderChunk plugin, so Terser has already minified the code it receives.
  2. Protects all chunks in one batch — every chunk whose name ends in .js, .mjs or .cjs. CSS, images and other assets are not touched.
  3. Deletes the chunk’s source map from the bundle, so dist never ships a .map that describes the unprotected code.
  4. Picks the engine. With no API key and only free options it protects locally; nothing is uploaded. With a key, paid options run on the hosted engine.

Why Terser? Without a minifier, Rollup keeps your top-level function and variable names, and the free engine renames local variables only. Terser shortens the top-level names first.

Real Output

Before and after, from the same source

The sample is a pricing table and a licence-key checksum split over three modules. First, the chunk Rollup and Terser produce on their own; then the same build with the plugin added (the seven-line comment that opens the free output is left out here).

Rollup + Terser only — 523 bytes
const t=[{name:"starter",seats:5,monthly:29},{name:"team",seats:25,monthly:49},{name:"business",seats:100,monthly:99}];function n(t){let n=0;for(const e of t+"acme-license-v2")n=31*n+e.charCodeAt(0)>>>0;return n.toString(16)}...
With jsoProtector() — 1,261 bytes
const t=[{name:"\x73\x74\x61\x72\x74\x65\x72",seats:5,monthly:29},{name:"\x74\x65\x61\x6d",seats:25,monthly:49},{name:"\x62\x75\x73\x69\x6e\x65\x73\x73",seats:100,monthly:99}];function n(b){let a=0;for(const c of b+"\x61\x63\x6d\x65\x2d\x6c\x69\x63\x65\x6e\x73\x65\x2d\x76\x32")a=31*a+c.charCodeAt(0)>>>0;return a.toString(16);}...

Of the 738 bytes added, 546 are the free-version comment; the rest is the hex encoding of the strings. Running both files printed the same line: {"quote":{"tier":"team","total":490,"perSeat":40.83},"valid":true,"forged":false}. Encoded strings are a first layer — any beautifier decodes \x escapes — which is why the paid presets add string encryption, flattening and the other transforms shown step by step here.

Options

The options you are most likely to need

Pass them to jsoProtector({ ... }). The same names work in a jso.config.json shared with the CLI.

OptionWhat it doesExample
presetWhich transforms run. free is the default with no API key; standard, balanced and maximum need a plan.preset: "standard"
excludeGlob patterns for chunks to leave unprotected, such as a vendor chunk.exclude: ["**/vendor-*.js"]
includeProtect only the chunks that match.include: ["**/app-*.js"]
reservedNamesNames the renamer must keep, for code that is looked up by name.reservedNames: ["initWidget"]
removeSourceMapsOn by default. Set false only for maps you keep private.removeSourceMaps: false
manifestWrites a JSON record of what was protected, with hashes, for release evidence.manifest: "dist/jso-manifest.json"
maxGrowthRatioFails the build if a chunk grows more than this many times.maxGrowthRatio: 4

For paid presets, give the build your credentials through the JSO_API_KEY and JSO_API_PASSWORD environment variables (or run npx javascriptobfuscator login once on a workstation). Keep them out of rollup.config.mjs.

Free Engine

What stays readable without a plan

  • Top-level names in an ES-module chunk, unless a minifier shortens them first (hence Terser above).
  • Property names and exports such as seats, monthly or a library’s public API — renaming those would break callers.
  • Strings written as template literals (backticks) are left as written; double- and single-quoted strings are encoded.
  • Program structure — the free engine does not flatten control flow or compress.
Going Further

Stronger presets, same plugin

  • preset: "standard" (Basic, $29/month) adds moving strings into an array and the Compressor.
  • preset: "balanced" adds string encryption, deep obfuscation, code transposition and flattening.
  • preset: "maximum" adds dead code, global renaming and the runtime defences.
  • See which options each preset turns on, and the plan each needs, in the online tool; its Copy as CLI command button exports your choice.
Frequently Asked

Rollup obfuscation questions

Does the plugin order matter in a Rollup config?

Not for minifiers. @rollup/plugin-terser works in renderChunk; the obfuscator works later, in generateBundle, so it always sees the minified chunk whichever order you list them in.

Do I need an account to obfuscate a Rollup build?

No. With no API key the plugin protects on your own machine with the free engine: local variables renamed and string literals encoded, nothing uploaded, no rate limit. The output starts with a short comment naming the product. Paid options such as the Compressor and Flat Transform run on the hosted engine and need a plan.

What happens to Rollup source maps?

The plugin deletes the .map file of every chunk it protects, because a map would describe the unprotected code. Pass removeSourceMaps: false only if the maps stay private; to read production stack traces, use the identifier map and symbolication instead.

Does it protect .mjs and .cjs library builds?

Yes. Every chunk whose file name ends in .js, .mjs or .cjs is protected; CSS, images and other assets are left alone. Before you protect a library you publish to npm, read should you obfuscate an npm package?

Other Build Tools

Not using Rollup?

The same package protects esbuild, Rspack, Parcel and Gulp builds, or any output folder from the command line.