Obfuscation presets

The five protection levels from minify to extreme, the complexity target each one sets, and what the zero-config default (light) already guarantees.

A preset is a named level of protection. One name sets the complexity target; output size follows from that target. Setting complexity explicitly overrides the target, and a value set directly always applies to every token, even on light.

The five obfuscation presets ranked by complexity target, with light marked as the defaultextremetarget 25hardtarget 12mediumtarget 7lighttarget 5DEFAULTminifytarget 0Zero config gives you light: string literals encoded. Step up per project.

The ladder

PresetcomplexityTypical gzipped sizeString floorReach for it when
minify01.35x to 1.47xoffNo obfuscation. Identifiers are renamed and the code is compressed; string literals stay readable.
light51.92x to 2.41xonDefault. Adds string encoding to minify and rewrites syntax, with no structural layers. The right baseline for most projects.
medium77.98x to 9.73xonProduction code you want meaningfully harder to follow.
hard129.40x to 11.06xonCode that matters: pricing, gating, license checks, client-side business logic.
extreme2512.86x to 14.27xonThe highest applied complexity. Aim it at a file or region rather than a whole bundle.

Gzipped output measured against the same file already minified, on four real minified production bundles, with AfterPack 0.2.0 on 2026-09-29; Performance has the method.

The string floor is on for any positive target.

The default is light

Run npx afterpack@latest dist/ with no --preset, or install a plugin and configure nothing, and you get light.

$ npx afterpack@latest dist/
npx afterpack@latest dist/ --preset=hard    # step up

Flags join their value with =. A space-separated value is rejected as an unknown flag.

light is the zero-config floor. Every string literal it can rewrite goes through a runtime decoder. It renames identifiers and rewrites syntax, and it adds no structural layers. From engine 0.2.3, loops are no longer rewritten into callback helpers at minify and light, which removes the 2-5x slowdown those helpers caused in hot loops. At light, property and global names still go through encoded constants, which has some cost in very hot code. Some strings stay readable at every preset: directive prologues, typeof comparands, property names the runtime needs, and export names. The Protection Map lists them, and the engine reports the count and byte total on every build. Raising the preset to medium or above adds structural work on every token: inflation, control-flow reshaping, and strings and integers keyed to a shared runtime value. It does not change string readability, which is already at its floor.

minify is the one rung that turns the floor off. From engine 0.2.3, it only renames identifiers and compresses the code, with no obfuscation applied. Engine 0.2.2 also rewrote syntax at minify.

Size budget

Every output carries the engine's fixed runtime overhead — the decoders and the integer-expression interpreter — so a file of a few hundred bytes comes out proportionally larger than the ranges above suggest. Read a ratio on a real bundle, not a small file.

inflation.max is unset by default, so the complexity target alone decides the output size. Set it yourself when you have a real size budget you would rather fail than exceed. Hitting an explicit value is an error: DIAG_SIZE_CAP_REACHED, no output, exit 3 from the CLI, and a failed build from a plugin. From engine 0.2.3, under-protected output is never written in its place: the finished file is checked against the limit, and string encoding always completes first. In engine 0.2.2, a tight limit could stop string encoding early and leave some strings readable.

Setting the target directly

complexity is an unbounded non-negative number. --complexity=200 is a valid Free-tier override, well past what extreme sets.

npx afterpack@latest dist/ --preset=hard --complexity=40

That is hard with its complexity target overridden to 40; nothing else about the preset changes. Passing a preset name to --complexity is an error.

Applying a preset to one region

--preset sets the whole bundle uniformly; that is Free. Concentrating a heavier preset on one function or file is a Pro feature, through a source directive:

/* @afterpack preset=extreme */
function pricingEngine(plan, region) {
  return resolve(plan, region);
}
/* @afterpack end */

Directives are block comments only, read from your source by the plugin. See Directives for the grammar and the tier rules.

Next