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 ladder
| Preset | complexity | Typical gzipped size | String floor | Reach for it when |
|---|---|---|---|---|
minify | 0 | 1.35x to 1.47x | off | No obfuscation. Identifiers are renamed and the code is compressed; string literals stay readable. |
light | 5 | 1.92x to 2.41x | on | Default. Adds string encoding to minify and rewrites syntax, with no structural layers. The right baseline for most projects. |
medium | 7 | 7.98x to 9.73x | on | Production code you want meaningfully harder to follow. |
hard | 12 | 9.40x to 11.06x | on | Code that matters: pricing, gating, license checks, client-side business logic. |
extreme | 25 | 12.86x to 14.27x | on | The 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 upFlags 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=40That 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
- Configuration: every key a preset touches.
- CLI reference: the flags that select a preset.
- Directives:
skiporpreset=<name>for one span. - How AfterPack works: what a higher target actually buys against a static reader.
- Best practices: what to push harder and what to leave alone.