The npm CLI workflow lets JavaScript teams run JavaScript Obfuscator from package scripts and CI while still using the same hosted API and dashboard API keys.
Install the wrapper
The wrapper is designed as a lightweight Node.js command that sends generated JavaScript files to the JavaScript Obfuscator HTTP API and writes protected output to a release folder.
npm install --save-dev jso-protector
During local development in this repository, the package scaffold lives at packages/jso-protector.
Create a config file
npx jso-protector --init
The generated jso.config.json points at HttpApi.ashx, reads dashboard credentials from environment variables, and protects the JavaScript files emitted by your existing build.
{
"$schema": "./node_modules/jso-protector/jso.config.schema.json",
"endpoint": "https://www.javascriptobfuscator.com/HttpApi.ashx",
"apiKey": "$JSO_API_KEY",
"apiPassword": "$JSO_API_PASSWORD",
"projectName": "browser-release",
"input": "dist",
"output": "dist-protected",
"preset": "balanced",
"extensions": [".js", ".jsx"],
"exclude": ["**/*.map", "**/vendor/**", "**/*-obfuscated.js"],
"copyAssets": true,
"assetExclude": ["**/*.map"],
"reservedNames": ["^PublicApi$", "^keep_"],
"options": {
"OptimizationMode": "Web"
}
}
The package includes jso.config.schema.json for editor autocomplete and validation.
--config accepts JSON files and trusted CommonJS config files. When no config path is provided, the CLI looks for jso.config.json, then jso.config.cjs, then jso.config.js.
module.exports = ({ env }) => ({
apiKey: "$JSO_API_KEY",
apiPassword: "$JSO_API_PASSWORD",
input: env.CI ? "dist" : "demo-dist",
output: "dist-protected",
preset: "balanced",
reservedNames: ["^PublicApi$"],
options: {
LockDomain: Boolean(env.RELEASE_DOMAIN),
LockDomainList: env.RELEASE_DOMAIN || ""
}
});
JavaScript config files execute as Node.js code, so only load config files from your own repository or another trusted source. Use JSON config when you want schema validation and no executable config logic.
By default, the CLI ignores source maps, node_modules, and *-obfuscated.js files. When the output folder is nested under the input folder, reruns also skip that output folder so already protected files are not protected again. The CLI copies non-protected release assets such as HTML, CSS, fonts, and images into the output folder. Use assetExclude to keep source maps or other development-only files out of the protected release.
Choose a protection preset
Presets make command-line protection repeatable without forcing every team to hand-pick low-level API flags on day one.
standard Core string encoding, string movement, name replacement, and compression. |
balanced Adds short local names, deep obfuscation, code transposition, string encryption, and flat transform. |
maximum Adds member/global renaming, member movement, and low dead-code insertion for higher-friction output. |
npx jso-protector --preset maximum --input dist --output dist-protected
Use options to override or extend a preset with API options such as LockDomain, LockDomainList, LockDate, DeepObfuscate, or FlatTransform.
Use the npm presets and options reference for inspect commands and common option groups.
Preserve public names
When a framework, external customer script, or integration calls a public symbol by name, preserve it with reservedNames. Each entry is treated as a regular expression and sent to the API as the Variable Exclusion List.
{
"preset": "balanced",
"reservedNames": ["^PublicApi$", "^renderWidget$", "^keep_"]
}
You can also set variableExclusion or options.VariableExclusion directly when you already have a multiline exclusion list.
Reuse an online JSON preset
Export a preset from the online obfuscator, then import it into the CLI so review settings and build settings do not drift.
npx jso-protector --config jso.config.json --web-preset javascript-obfuscator-preset.json --dry-run --json
A CLI config can also reference the file with "webPreset": "javascript-obfuscator-preset.json". The imported preset maps Standard options, premium preview features, and the Variable Exclusion List into the HTTP API payload.
Set API credentials
Copy the API Key and API Password values from the dashboard. These values are already base64 encoded for API use.
set JSO_API_KEY=base64-api-key-from-dashboard
set JSO_API_PASSWORD=base64-api-password-from-dashboard
On macOS or Linux shells, use export JSO_API_KEY=... and export JSO_API_PASSWORD=.... Keep real credentials in environment variables or CI secrets, not committed config files.
The CLI also accepts long-form aliases: JAVASCRIPT_OBFUSCATOR_API_KEY, JAVASCRIPT_OBFUSCATOR_API_PASSWORD, and JAVASCRIPT_OBFUSCATOR_ENDPOINT.
Add npm scripts
Run JavaScript Obfuscator after your normal build, then test the protected output before deployment.
{
"scripts": {
"build": "vite build",
"protect": "jso-protector --config jso.config.json",
"protect:dynamic": "jso-protector --config jso.config.cjs",
"release": "npm run build && npm run protect && npm run smoke"
}
}
Protect one file through a pipe
Use stdin mode for small custom scripts and build steps that already know which single file should be protected.
jso-protector --stdin --stdout --file-name app.js < dist/app.js > dist/app.protected.js
Omit --stdout and pass --output when you want the CLI to write the result as output/file-name.
jso-protector --stdin --file-name app.js --output dist-protected < dist/app.js
For direct single-file migration scripts, pass the input path directly. When no --output or config output is set, the CLI writes a sibling -obfuscated.js file.
jso-protector src/app.js
Override options from CI
Use repeatable --option Name=value flags to adjust API options without editing shared config files. Values true, false, null, and numbers are parsed into their JSON equivalents.
jso-protector --config jso.config.json --option LockDomain=true --option LockDomainList=example.com
Use repeatable --include flags to replace the protected-file include list for one job. Use repeatable --exclude and --asset-exclude flags to add job-specific exclusions without removing the config defaults.
jso-protector --config jso.config.json --include "assets/*.js" --exclude "**/legacy/**" --asset-exclude "**/*.license"
Use repeatable --reserved-name flags when a release job needs to preserve public API names or framework entry points.
jso-protector --config jso.config.json --reserved-name "^PublicApi$" --reserved-name "^renderWidget$"
When replacing existing javascript-obfuscator package scripts, the CLI accepts familiar flags and maps them to hosted API options.
jso-protector dist --output dist-protected --options-preset high-obfuscation --control-flow-flattening --string-array-encoding rc4 --reserved-names "^PublicApi$"
Mapped compatibility flags include --options-preset, --string-array, --string-array-encoding, --unicode-escape-sequence, --control-flow-flattening, --dead-code-injection, --dead-code-injection-threshold, --identifier-names-generator, --rename-globals, --rename-properties, --reserved-names, --domain-lock, --target, and --compact. --options-preset default and low-obfuscation map to standard, medium-obfuscation maps to balanced, and high-obfuscation maps to maximum.
The CLI also accepts common review-only flags such as --source-map, --self-defending, --debug-protection, --disable-console-output, --reserved-strings, --numbers-to-expressions, --seed, --split-strings, and string-array wrapper flags so older scripts do not fail immediately. These emit compatibility warnings when there is no one-to-one hosted API mapping.
Use --explain-compat self-defending --json to inspect one mapped or review-only option before editing older scripts.
Protect marked HTML and conditional regions
Pass --parse-html when built HTML files contain inline scripts that should be protected. HTML files require this explicit mode before protection, so the CLI will not send a whole HTML document to the JavaScript API path by accident. Only marked inline scripts are sent to the API. Unmarked inline scripts are preserved, while marked external scripts and marked module scripts fail clearly because HTML mode protects inline script contents only.
<script data-javascript-obfuscator>
window.releaseWidget = createWidget();
</script>
Use --honor-conditional-comments when source uses javascript-obfuscator:disable and javascript-obfuscator:enable markers. Without the flag, the CLI fails with a clear warning when markers are found. With the flag, enabled regions are protected and disabled regions are copied back verbatim. Markers must be balanced; unmatched or nested disabled regions fail before any source is sent.
console.log("protect this");
// javascript-obfuscator:disable
console.log("keep this exact local code");
// javascript-obfuscator:enable
console.log("protect this too");
--dry-run --json and --release-check --json include a processing summary with the API item count and any transformed files, so CI can show exactly which files will be split before protection.
Local desktop and offline path
Run jso-protector --local-only --json when npm users need a terminal reminder that validation, dry-run planning, doctor, and release-check reports are local, while npm CLI protection uses the hosted HTTP API. Use the desktop/local workflow when source cannot leave the machine.
Use the Node API
Custom build scripts can call JavaScript Obfuscator directly without spawning the CLI.
const { obfuscate, obfuscateMultiple, getOptionsByPreset, protectCode, protectFiles, planProtection } = require("jso-protector");
const obfuscationResult = await obfuscate("console.log('release');", {
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
controlFlowFlattening: true,
identifierNamesGenerator: "hexadecimal",
reservedNames: ["^PublicApi$"],
stringArrayEncoding: ["rc4"]
}, "app.js");
console.log(obfuscationResult.getObfuscatedCode());
console.log(obfuscationResult.toString());
const multipleResults = await obfuscateMultiple({
"foo.js": "var foo = 1;",
"bar.js": "var bar = 2;"
}, {
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
...getOptionsByPreset("balanced")
});
console.log(multipleResults["foo.js"].getObfuscatedCode());
const protectedCode = await protectCode({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced"
}, "console.log('release');", "app.js");
const plan = planProtection({
input: "dist",
output: "dist-protected",
preset: "balanced",
exclude: ["**/*.map", "**/vendor/**", "**/*-obfuscated.js"]
});
console.log(`Protecting ${plan.summary.files.length} file(s).`);
await protectFiles({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
input: "dist",
output: "dist-protected",
preset: "balanced",
manifest: "dist-protected/jso-manifest.json"
});
Use obfuscate(code, options, fileName) and obfuscateMultiple(sourceCodesObject, options) when migrating from javascript-obfuscator style Node scripts. These methods are asynchronous because they call the hosted API, and their result objects include getObfuscatedCode(), toString(), getSourceMap(), and getIdentifierNamesCache() for compatibility-friendly call sites. They use the same marked HTML and conditional-comment planning as the CLI, so parseHtml and honorConditionalComments work for object-map inputs too. Source maps and identifier caches return null because the hosted API workflow is designed to remove release source maps.
The Node API accepts common javascript-obfuscator option names directly and maps them to the closest hosted API options. Supported compatibility keys include optionsPreset, stringArray, stringArrayEncoding, controlFlowFlattening, deadCodeInjection, deadCodeInjectionThreshold, identifierNamesGenerator, renameGlobals, renameProperties, reservedNames, compact, and target. Use translateJavascriptObfuscatorOptions(sourceOptions, overrides) when tooling needs to inspect the mapped config before calling the API.
getOptionsByPreset("standard" | "balanced" | "maximum") returns a copy of the hosted API preset options for scripts that previously used preset lookup helpers.
Use protectFile(options, sourcePath, outputPath) for one physical source file, protectFiles(options) for configured file or folder protection, and planProtection(options) when CI needs a dry plan before the API call. The obfuscateFile, obfuscateFiles, and obfuscateDirectory aliases are available for teams that prefer obfuscator naming. These helpers use the same config merging, preset handling, asset copying, manifest writing, and size budget checks as the CLI.
TypeScript declarations are included for the Node API, Browserify, esbuild, Vite, Rollup, Webpack, Gulp, Grunt, and the Webpack loader entrypoint. No separate @types package is required.
Use the Browserify transform
The Browserify transform protects selected modules before bundling and passes excluded files through untouched.
const browserify = require("browserify");
const fs = require("fs");
const jsoProtector = require("jso-protector/browserify");
browserify("src/app.js", {
transform: [[jsoProtector, {
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
input: "src",
preset: "balanced",
include: ["**/*.js"],
reservedNames: ["^PublicApi$"],
manifest: "dist/jso-manifest.json"
}]]
})
.bundle()
.pipe(fs.createWriteStream("dist/app.js"));
The transform honors config files, filters, manifests, and size budgets.
Use the esbuild plugin
The esbuild entrypoint protects in-memory output files when write: false is used, and protects written outdir or outfile assets in place for normal builds.
const esbuild = require("esbuild");
const jsoProtector = require("jso-protector/esbuild");
await esbuild.build({
entryPoints: ["src/app.js"],
bundle: true,
outdir: "dist",
plugins: [
jsoProtector({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
exclude: ["vendor.js"]
})
]
});
Use the Vite plugin
The Vite entrypoint protects emitted JavaScript chunks after bundling and removes stale JavaScript source maps by default.
const jsoProtector = require("jso-protector/vite");
module.exports = {
plugins: [
jsoProtector({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
exclude: ["**/vendor/**"]
})
]
};
Use the Rollup plugin
The Rollup entrypoint protects generated chunks directly from generateBundle, using the same presets and API credentials as the CLI.
const jsoProtector = require("jso-protector/rollup");
module.exports = {
plugins: [
jsoProtector({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
exclude: ["vendor.js"]
})
]
};
Use the Webpack plugin
The Webpack entrypoint protects emitted JavaScript assets during processAssets and removes stale .js.map assets by default.
const JsoProtectorWebpackPlugin = require("jso-protector/webpack");
module.exports = {
plugins: [
new JsoProtectorWebpackPlugin({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
exclude: ["vendor.js"]
})
]
};
Use the Webpack loader
The Webpack loader protects selected modules before bundling. For complete release bundles, prefer the Webpack plugin because it protects final emitted assets and removes stale source maps.
module.exports = {
module: {
rules: [{
test: /\.js$/,
include: /src/,
use: [{
loader: "jso-protector/webpack-loader",
options: {
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
include: ["src/*.js"],
reservedNames: ["^PublicApi$"]
}
}]
}]
}
};
The loader disables Webpack caching for protected modules, returns no source map after protection, and honors config files, filters, manifests, and size budgets.
Use the Gulp plugin
The Gulp entrypoint protects buffered Vinyl files in legacy release tasks, passes non-JavaScript assets through, and removes stale .js.map files by default.
const { dest, src } = require("gulp");
const jsoProtector = require("jso-protector/gulp");
function protect() {
return src(["dist/**/*.js", "dist/**/*.{css,html,png,svg}", "!dist/**/*.map"], { base: "dist" })
.pipe(jsoProtector({
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
preset: "balanced",
exclude: ["**/vendor/**"],
manifest: "dist-protected/jso-manifest.json"
}))
.pipe(dest("dist-protected"));
}
Use the Grunt task
The Grunt entrypoint protects JavaScript file mappings in legacy release tasks and writes protected files to each mapping's dest. Use a copy task for non-JavaScript assets.
module.exports = function configureGrunt(grunt) {
grunt.initConfig({
jsoProtector: {
release: {
options: {
apiKey: process.env.JSO_API_KEY,
apiPassword: process.env.JSO_API_PASSWORD,
input: "dist",
output: "dist-protected",
preset: "balanced",
exclude: ["**/vendor/**"],
manifest: "dist-protected/jso-manifest.json"
},
files: [{
expand: true,
cwd: "dist",
src: ["**/*.js", "!**/*.map"],
dest: "dist-protected"
}]
}
}
});
require("jso-protector/grunt")(grunt);
grunt.registerTask("protect", ["jsoProtector:release"]);
};
Filter build outputs
For Browserify, esbuild, Vite, Rollup, Webpack, Webpack loader, Gulp, and Grunt integrations, include and exclude match emitted asset names. Use them to protect first-party release chunks while leaving vendor bundles, polyfills, and framework runtime files untouched.
jsoProtector({
preset: "balanced",
include: ["assets/*.js"],
exclude: ["**/vendor/**", "**/polyfills-*.js"],
maxOutputBytes: 250000,
maxGrowthRatio: 8
});
The Browserify, esbuild, Vite, Rollup, Webpack, Webpack loader, Gulp, and Grunt integrations also honor manifest, maxOutputBytes, and maxGrowthRatio. A failed size budget stops the build before protected chunks replace the original bundle output.
Dry run before calling the API
Use dry-run mode to verify the files, output folder, endpoint, and enabled options.
npx jso-protector --config jso.config.json --dry-run --json
If another build step already copies assets, run jso-protector --no-copy-assets or set "copyAssets": false.
Run doctor before release
Use --release-check before a release job when you want one CI-friendly report that combines config validation, dry-run file planning, and doctor checks without sending source code to the API.
jso-protector --config jso.config.json --release-check --json
jso-protector --config jso.config.json --release-check --strict --json
Use --validate-config for a fast static check of config shape, endpoint format, credentials presence, path settings, budget values, and local option-name references without sending source code to the API.
jso-protector --config jso.config.json --validate-config --json
Unknown option names are reported as warnings so teams can catch typos while still using advanced HTTP API options. Add --strict when validation warnings should fail CI.
Doctor goes further by checking matched files, copied assets, output readiness, presets, and enabled options without sending source code to the API.
jso-protector --config jso.config.json --doctor --json
Add --check-api to --release-check or --doctor when CI should send a tiny live request to verify the endpoint and credentials.
jso-protector --config jso.config.json --release-check --check-api --json
jso-protector --config jso.config.json --doctor --check-api --json
Write a release manifest
Use --manifest or "manifest" in config to write a JSON record after successful protection. The CLI and build plugins support manifests. The manifest includes project metadata, preset, enabled option names, protected file sizes and SHA-256 hashes, and copied asset hashes.
jso-protector --config jso.config.json --manifest dist-protected/jso-manifest.json
jsoProtector({
preset: "balanced",
manifest: "dist/jso-manifest.json"
});
Store the manifest with release artifacts when you need a traceable record of what was protected.
Enforce release size budgets
Use size budgets to fail CI when protected output grows beyond your release limits. --max-output-bytes or "maxOutputBytes" checks each protected file size, and --max-growth-ratio or "maxGrowthRatio" compares protected bytes against original source bytes.
jso-protector --config jso.config.json --max-output-bytes 250000 --max-growth-ratio 8
The same budget settings work in plugin config, so Browserify, Vite, Rollup, Webpack, Webpack loader, esbuild, Gulp, and Grunt builds can fail before oversized protected chunks are written.
Inspect resolved config safely
Use --print-config to inspect the final config after presets, config files, environment variables, and CLI overrides are merged. API credentials are redacted as [set] or [missing].
jso-protector --config jso.config.json --print-config --json
Check CLI version metadata
Use --version for a quick parity check in install scripts. Add --json when CI needs package metadata and the default hosted API endpoint.
jso-protector --version
jso-protector --version --json
Copy examples and CI templates
The package includes copyable example setups for CLI, Node API, Browserify, esbuild, Vite, Rollup, Webpack, Webpack loader, Gulp, and Grunt workflows, plus CI templates for GitHub Actions, GitLab CI, and Azure Pipelines.
node_modules/jso-protector/examples/
node_modules/jso-protector/ci/
Run a live API smoke test
After setting JSO_API_KEY and JSO_API_PASSWORD, run a tiny API test before wiring protection into a larger release job.
npm run smoke:api
The package smoke test protects the sample file in dist/ and writes the result to dist-protected/. Use the regular dry-run smoke test for offline validation.
GitHub Actions example
Store JSO_API_KEY and JSO_API_PASSWORD as encrypted repository or organization secrets. Run release preflight and protection after your normal frontend build.
name: protected-release
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm run verify:package --if-present
- name: Release preflight
run: npx jso-protector --config jso.config.json --release-check --json
env:
JSO_API_KEY: ${{ secrets.JSO_API_KEY }}
JSO_API_PASSWORD: ${{ secrets.JSO_API_PASSWORD }}
- name: Protect JavaScript
run: npx jso-protector --config jso.config.json --manifest dist-protected/jso-manifest.json
env:
JSO_API_KEY: ${{ secrets.JSO_API_KEY }}
JSO_API_PASSWORD: ${{ secrets.JSO_API_PASSWORD }}
- run: npm run smoke
- uses: actions/upload-artifact@v4
with:
name: protected-dist
path: |
dist-protected/
dist-protected/jso-manifest.json
Release checklist
- Build unprotected JavaScript into a temporary output folder.
- Run
jso-protector --release-check --json to validate config, confirm the file list, and check paths, assets, output readiness, presets, and enabled options before source is sent.
- Protect into a separate output folder such as
dist-protected, including required static assets and dist-protected/jso-manifest.json.
- Run browser smoke tests against the protected output.
- Publish only the protected artifacts and release manifest.
How this compares with desktop projects
npm CLI wrapper Best for Node-based builds, CI jobs, package scripts, and teams that want config in source control. |
Desktop project Best for visual setup, embedded JavaScript files, local-only workflows, and larger batch projects managed outside npm. |
Important: the HTTP API is for paid accounts. If your policy requires code to stay local, use the desktop workflow instead of the hosted API.