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.

Stack-trace Symbolication

  • Basic
  • jso-symbolicate

Production crashes in obfuscated code arrive as stacks full of _0x2a3b and _0xff01 names that nobody can debug. jso-symbolicate turns those back into human names — locally on your machine, no upload, no third-party service.

Try it interactively › — paste a map and a stack, see the demangling happen client-side.

How it works

Every protected build returns an identifier map alongside the obfuscated code. The map records which mangled name corresponds to which original symbol. Keep the map next to your release artifacts (S3 bucket, build server, release tarball) and feed it to jso-symbolicate together with any crashing stack trace from that build.

Nothing about this is network-bound. The mapping is a plain JSON or text file. The substitution is regex-based and runs in Node.js without any external dependencies. You can use jso-symbolicate from CI, from your laptop, or from a private offline support environment.

Capture the identifier map

The JSO HTTP API response includes Report.GlobalIdentifierMap and Report.MemberIdentifierMap. Persist these with your release artifacts using whatever folder convention you already keep:

release/
  build-2026-05-19/
    app.protected.js
    app.protected.js.report.json      «— saved API response
    SHA256SUMS

The npm CLI jso-protector writes the report automatically when you pass --report ./out.report.json.

Install the symbolicator

npm install --save-dev jso-symbolicate

Symbolicate a stack trace

cat crash.txt | npx jso-symbolicate --map ./build-2026-05-19/app.protected.js.report.json

The CLI writes the demangled stack to stdout and a one-line summary to stderr:

Error: invalid license signature
    at calculateLicenseHash (app.js:1:25)
    at verifySignature (app.js:2:10)
    at boot (app.js:3:5)
jso-symbolicate: 3 substitution(s) across 184 map entries

Annotate instead of replace

When you want to keep the mangled identifier visible (for example, so the demangled trace is still searchable against your raw crash logs), use --annotate:

cat crash.txt | npx jso-symbolicate --map ./report.json --annotate
    at _0x2a3b /* calculateLicenseHash */ (app.js:1:25)

Detect wrong-map cases

A stack trace symbolicated against the wrong build's map produces zero substitutions. --json emits structured output you can check in CI:

cat crash.txt | npx jso-symbolicate --map ./report.json --json
{
  "stack": "...",
  "hits": 0,
  "mapEntries": 184,
  "perLine": [0, 0, 0]
}

When hits === 0 and mapEntries > 0, the map and the trace are from different builds. Fail the support ticket triage before someone wastes an hour reading a stack against the wrong release.

Match the trace to the build

The HTTP API response now includes a Report.BuildId and Report.PolymorphismFingerprint. Have your runtime crash reporter capture BuildId from a global injected at protection time so every uploaded crash carries the right release identifier. Then your symbolication script can look up the matching report.json automatically.

Error-reporter integrations

Six reporters have first-class subpath exports. Pick the one that matches your runtime; the lookup table and the rest of the wiring stay identical.

Sentry
jso-symbolicate/sentry exports createSentryEventProcessor(lookup). Drop into Sentry.init({ beforeSend }). Walks event.exception.values[].stacktrace.frames[], threads, breadcrumbs, message, and inline source context.
Bugsnag
jso-symbolicate/bugsnag exports createBugsnagOnError(lookup). Wire into Bugsnag.start({ onError }). Rewrites errorClass, errorMessage, and stacktrace[].method.
Rollbar
jso-symbolicate/rollbar exports createRollbarTransform(lookup). Pass as the transform option to new Rollbar(). Walks payload.data.body.trace, trace_chain, message, telemetry breadcrumbs.
Datadog Browser RUM / Logs
jso-symbolicate/datadog exports createDatadogBeforeSend(lookup). Wire into datadogRum.init({ beforeSend }) or datadogLogs.init({ beforeSend }). Rewrites error.message, error.type, error.stack, action names, log message bodies.
Honeybadger
jso-symbolicate/honeybadger exports createHoneybadgerBeforeNotify(lookup). Pass to Honeybadger.configure({ beforeNotify }). Handles both raw-string and structured-array backtrace shapes.
Raygun
jso-symbolicate/raygun exports createRaygunBeforeSend(lookup). Pass to rg4js("onBeforeSend", ...). Covers both Raygun4JS and Raygun4Node payload shapes (Pascal-case and camelCase).

All six implementations swallow transform errors and tag the event with jso_symbolicated (or jso_symbolicate_error if something went wrong). A broken lookup will never block crash reporting.

Programmatic use

const { buildLookup, demangleStack } = require("jso-symbolicate");
const report = JSON.parse(fs.readFileSync("report.json", "utf8"));
const lookup = buildLookup(
    report.Report.GlobalIdentifierMap,
    report.Report.MemberIdentifierMap
);
const readable = demangleStack(rawStack, lookup);

Source Map v3

If you need a Source Map v3 (.map) file for Sentry, Datadog, or Bugsnag automatic symbolication, contact support — the underlying identifier-map data is sufficient to generate one. Direct Source Map emission is on the roadmap and will ship as jso-symbolicate --emit-source-map.

Privacy stance

Stack traces routinely include customer data: file paths, internal route names, occasionally user identifiers in URLs. jso-symbolicate never sends any of that to JSO or anyone else. The map you bought was generated by the API; the demangling happens entirely on your machine.