Documentation

Guides for protecting production JavaScript

Reference guides for release workflows, command-line usage, cross-file protections, and the desktop app.

Inside The Docs

Practical guides, not placeholder pages.

How-to guides Start with release sequencing and command-line usage, then move into feature-specific references.
Advanced protection Browse cross-file controls like Replace Globals and Protect Members when a build spans multiple scripts.

Language clients

  • Node, Python, Go, .NET, Ruby, PHP, Rust
  • Yes
  • Yes

JSO ships server-side client libraries for seven languages. Every client targets the same HttpApi.ashx endpoint, sends the same JSON shape, parses the same Report back, and exposes the same three named presets. Pick the one that matches the language your build pipeline already runs in — there's no functional difference between them.

The client matrix

Language Package Install Min version HTTP transport Locally test-verified
Node jso-protector (npm) npm install --save-dev jso-protector Node ≥18 built-in fetch Yes — 152 tests
Python jso-protector (PyPI) pip install jso-protector Python ≥3.8 stdlib urllib (no requests dep) Yes — 8 tests
Go jso-protector-go go get github.com/javascriptobfuscator/jso-protector-go Go ≥1.21 stdlib net/http Tests written (no Go toolchain locally)
.NET JsoProtector (NuGet) dotnet add package JsoProtector .NET Standard 2.0 HttpClient (IHttpClientFactory-friendly) Yes — 8 xUnit tests
Ruby jso_protector (RubyGems) gem install jso_protector Ruby ≥2.7 stdlib net/http Tests written (no Ruby toolchain locally)
PHP javascriptobfuscator/jso-protector (Packagist) composer require javascriptobfuscator/jso-protector PHP ≥7.4 ext-curl when present; stream context fallback Tests written (no PHP 7.4+ locally)
Rust jso-protector (crates.io) cargo add jso-protector Rust ≥1.70 ureq (sync, rustls) Tests written (no Rust toolchain locally)
Java com.javascriptobfuscator:jso-protector (Maven Central) <dependency><groupId>com.javascriptobfuscator</groupId><artifactId>jso-protector</artifactId></dependency> JDK 11+ stdlib java.net.http.HttpClient Tests written (JDK 8 only locally)
Anything else Wire format spec + examples/curl/protect.sh curl + jq POSIX shell curl Syntax-checked

Shared design rules

Every client honors these invariants. If you switch languages, your protection code reads almost identically:

  • Same protect() surface. Inputs: files (map of filename to source), preset (one of standard, balanced, maximum), optional options override map, optional label (forwarded as ReleaseLabel).
  • Same Result shape. files (protected source by name), build_id (stable identifier for this run), polymorphism_fingerprint (short SHA-256 over output), report (full Report including identifier maps), raw (complete response body).
  • Env-var-first credentials. JSO_API_KEY / JSO_API_PASSWORD (or the long-form JAVASCRIPT_OBFUSCATOR_API_KEY / JAVASCRIPT_OBFUSCATOR_API_PASSWORD) read from the environment before falling back to constructor arguments. Use env vars on shared / CI machines.
  • Same three preset definitions. Standard, balanced, maximum. Explicit options always override preset defaults.
  • Typed error class. Error / Exception with the API's Type and ErrorCode when the server replies non-Succeed. Messages never include the API key or password.
  • No mandatory third-party deps. Except where the language ecosystem requires it (Rust has no stdlib HTTP client; we use ureq). Everywhere else: stdlib transport, no async runtime.

Side-by-side example

The same "protect app.js and print the BuildId" task in each language. The body of the program is structurally identical because the surface is.

Node:

npx jso-protector --config jso.config.json --label "$GIT_COMMIT" --report jso-report.json

Python:

from jso_protector import protect
result = protect(
    files={"app.js": open("dist/app.js").read()},
    preset="balanced", label=os.environ.get("GIT_COMMIT"))
print("BuildId:", result.build_id)

Go:

res, err := jso.Protect(ctx, jso.Request{
    Files: map[string]string{"app.js": string(src)},
    Preset: "balanced", Label: os.Getenv("GIT_COMMIT"),
})
log.Println("BuildId:", res.BuildID)

.NET:

var r = await client.ProtectAsync(new ProtectOptions {
    Files = new() { ["app.js"] = src },
    Preset = "balanced",
    Label = Environment.GetEnvironmentVariable("GIT_COMMIT"),
});
Console.WriteLine($"BuildId: {r.BuildId}");

Ruby:

result = JsoProtector.protect(
  files: { "app.js" => File.read("dist/app.js") },
  preset: "balanced", label: ENV["GIT_COMMIT"])
puts "BuildId: #{result.build_id}"

PHP:

$result = (new \JsoProtector\Client())->protect([
    'files' => ['app.js' => file_get_contents('dist/app.js')],
    'preset' => 'balanced',
    'label' => getenv('GIT_COMMIT') ?: null,
]);
echo "BuildId: {$result->buildId}\n";

Rust:

let result = Client::new().protect(ProtectRequest {
    files: HashMap::from([("app.js".into(), src)]),
    preset: Some("balanced".into()),
    label: env::var("GIT_COMMIT").ok(),
    ..Default::default()
})?;
println!("BuildId: {:?}", result.build_id);

Which client should you use?

  • Node-first build (Vite, Webpack, Next.js, Bun)jso-protector npm CLI is the richest surface: 8 bundler plugins, dry-run, release-check, manifest, report flags.
  • Python build / scripted release / ML deployjso-protector on PyPI. Stdlib-only, easy to wire into Bazel + py_binary.
  • Go-based DevOps toolingjso-protector-go. Stdlib + context.Context cancellation.
  • .NET Framework / .NET 8 / Unity / XamarinJsoProtector NuGet. .NET Standard 2.0, HttpClient-injectable.
  • Rails / Ruby scriptjso_protector gem. Stdlib-only.
  • Laravel / Symfony / WordPress buildjavascriptobfuscator/jso-protector on Packagist. ext-curl-preferred, no third-party dep.
  • Rust / Cargo-driven buildjso-protector crate. Sync via ureq, typed error enum.

The wire format is one HTTP POST per protection call. If your language isn't listed above, write the POST by hand — every JSO client implementation linked above is small enough (200–400 lines) to serve as the reference.

Symbolication remains Node-only today via jso-symbolicate. The identifier map in Report.GlobalIdentifierMap / Report.MemberIdentifierMap is plain JSON, though, so any of the language clients above can implement local demangling in 30 lines of code if needed.