Documentation

Language clients

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

Inside the Docs

Practical guides for real release work.

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 (jso-protector on npm)
  • Python, Go, .NET, Ruby, PHP, Rust, Java, Kotlin
  • Yes
  • Yes

The Node client is published today as jso-protector on npm. Reference clients for Python, Go, .NET, Ruby, PHP, Rust, Java, and Kotlin are implemented and tested in the product source tree, but they are not yet published to their public registries. Their package-manager commands will fail until release; contact us if one should be prioritised.

The client matrix

Language Package Availability 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) Unreleased — not on PyPI Python ≥3.8 stdlib urllib (no requests dep) Yes — 8 tests
Go jso-protector-go Unreleased — no public repository Go ≥1.21 stdlib net/http Tests written (no Go toolchain locally)
.NET JsoProtector (NuGet) Unreleased — not on NuGet .NET Standard 2.0 HttpClient (IHttpClientFactory-friendly) Yes — 8 xUnit tests
Ruby jso_protector (RubyGems) Unreleased — not on RubyGems Ruby ≥2.7 stdlib net/http Tests written (no Ruby toolchain locally)
PHP javascriptobfuscator/jso-protector (Packagist) Unreleased — not on Packagist PHP ≥7.4 ext-curl when present; stream context fallback Tests written (no PHP 7.4+ locally)
Rust jso-protector (crates.io) Unreleased — not on crates.io Rust ≥1.70 ureq (sync, rustls) Tests written (no Rust toolchain locally)
Java com.javascriptobfuscator:jso-protector (Maven Central) Unreleased — not on Maven Central JDK 11+ stdlib java.net.http.HttpClient Tests written (JDK 8 only locally)
Kotlin com.javascriptobfuscator:jso-protector-kotlin (Maven Central) Unreleased — not on Maven Central Kotlin ≥1.9, JDK 11+ stdlib java.net.http.HttpClient with coroutines Tests written (awaiting Kotlin CI)
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);

Java:

var result = client.protect(ProtectOptions.builder()
    .files(Map.of("app.js", Files.readString(Path.of("dist/app.js"))))
    .preset("balanced")
    .label(System.getenv("GIT_COMMIT"))
    .build());
System.out.println("BuildId: " + result.buildId());

Kotlin:

val result = client.protect(ProtectRequest(
    files = mapOf("app.js" to File("dist/app.js").readText()),
    preset = "balanced",
    label = System.getenv("GIT_COMMIT")
))
when (result) {
    is ProtectResult.Success -> println("BuildId: ${result.buildId}")
    is ProtectResult.Failure -> error(result.message)
}

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, Go, .NET, Ruby, PHP, Rust, Java, or Kotlin → use the wire-format reference for now or contact us to prioritise the relevant registry release. The implementations and examples below document the intended client shape, but they are not installable public packages today.

The wire format is one HTTP POST per protection call. If Node is not your release environment, use the documented wire format directly until the matching client is published.

Symbolication is available in the browser-only interactive demo and as a versioned jso-symbolicate direct download. The CLI is not registry-published. Identifier maps in Report.GlobalIdentifierMap and Report.MemberIdentifierMap remain plain JSON for local tooling.

Frequently asked questions

Which language clients can I install today?

The Node client, published on npm. Reference clients for Python, Go, .NET, Ruby, PHP, Rust, Java and Kotlin are implemented and tested in the product source tree, but they are not yet published to their public registries, so their package-manager commands will fail until release. That state is stated plainly on the matrix rather than implied, and if one of them should be prioritised for publication it is worth asking, because the ordering is driven by demand.

What can I use if my language has no published client yet?

The wire format directly. Every client is a thin wrapper over one HTTP POST per protection call, and that format is documented with examples including a shell script built on ordinary command line tools. Writing against it is a small amount of work, and because the clients share one surface, replacing your own code with an official client later is close to mechanical.

What stays the same when I switch languages?

The parts that matter for your build code. Every client exposes the same protect surface, taking a map of filename to source, one of the three presets, an optional options override map and an optional label. Every client returns the same result shape, with the protected files by name, a stable build identifier for the run, a short fingerprint over the output, the full report including identifier maps, and the complete response body. The three preset definitions are identical, and explicit options always override preset defaults.

How do the clients handle credentials?

Environment variables first, constructor arguments as a fallback. Both the short and long forms of the key and password variables are read from the environment before anything passed in code is consulted, which is what makes the environment the right place for them on shared and continuous integration machines. Error messages never include the key or the password, so a failed call can be logged without leaking either.

What dependencies does adding a client pull in?

As close to none as each ecosystem allows. The clients use the standard library transport wherever one exists, with no async runtime required, so most of them add nothing to your dependency tree at all. The single exception is where the language ships no standard HTTP client and one has to be chosen. That constraint is deliberate, because a release-time tool that drags in a dependency tree is a tool that has to be audited before every upgrade.

How do errors surface across the clients?

Through a typed error or exception class carrying the API's own type and error code whenever the server replies with anything other than success. Because the shape is the same everywhere, error handling written against one client transfers to another with only syntax changes, which matters most in a release pipeline where the failure path is the code least often exercised and most often needed.

Try this in the online obfuscator

Paste your own code and see this option applied, or compare plans for larger projects and the desktop app.

Try It Free See Pricing