Readable stack traces
Open Errors → Build artifacts. JavaScript maps and native symbols belong to the selected application environment. Uploads are private; existing errors resolve when opened, even if they arrived before the artifact.
JavaScript builds
- Build JavaScript with external version 3 source maps, including original source content if you want code excerpts.
- Install
@saaspro/nodeas a development dependency. Run the command below on each bundle and its matching map, before compression, integrity hashing and deployment. - Upload the maps privately and deploy those exact modified bundles. Do not include maps in your public assets.
pnpm exec spm-artifacts inject ./dist/app.js ./dist/app.js.map
pnpm exec spm-artifacts upload ./dist/app.js.map
Set SPM_HOST to your console origin, SPM_APP_ID to the application ID, SPM_ENV to the environment slug, and SPM_API_TOKEN to a Management API token with write scope and errors.write. Keep that token in CI secrets. The command accepts HTTPS or loopback development hosts, refuses redirects, and never prints response bodies or credentials.
Injection creates a deterministic 128-bit debug ID, registers it before application startup and adjusts generated mappings. JavaScript directive prologues and hashbangs are preserved. Repeating injection on the unchanged pair is safe; rebuilding or modifying either file requires a fresh pair. Browser and Node SDKs associate the registered ID with captured stack frames automatically. React Native's transport uses the same frame contract; inject the JavaScript bundle before it is packaged. Runtime engines without usable JavaScript stack locations must supply frames explicitly. Registration is bounded to 512 entries per runtime. If that limit is exceeded, automatic debug-ID association stops; use explicit frame IDs or start a fresh runtime. Multiple loaded builds at the same URL are treated as ambiguous.
An event's explicit debug ID selects only that build in its environment, even if the CDN renamed the file or no release was supplied. An unknown ID never falls back to another map. Without a debug ID, supply both the SDK release and deployed bundle URL/path:
pnpm exec spm-artifacts upload ./dist/app.js.map --release web-2026-09-07 --bundle https://app.example.com/assets/app.js
Query strings and fragments are ignored. A path-only upload matches that path on any origin within the same application, environment and release. Avoid reusing a release/path pair for different builds. The debugId upload field may override a missing embedded ID but must agree with an embedded debug_id/debugId.
Indexed maps
Embedded indexed version 3 maps are flattened during upload. Nested offsets, source roots and ignore lists are preserved. Sections must be ordered and non-overlapping; external section URLs are rejected and are never fetched. Limits are 5 MiB, 500 sections, nesting depth 8, 50,000 sources, 100,000 generated lines and 500,000 mappings. Supply the debug ID on the root map.
These rules follow the ECMA-426 source-map format. For build-ID injection, embedded sections are flattened into an ordinary map before positions are adjusted.
Native symbols
Export the exact binary's debug information to a Breakpad .sym file with your platform's dump_syms tool, preserving its MODULE identifier and architecture. The Breakpad symbol format defines the portable records produced from native debug information.
pnpm exec spm-artifacts upload-native ./build/app.sym
Capture physical frames from your native crash/error bridge and pass them to captureException:
spm.captureException(new Error('Native checkout failed'), {
frames: [{
file: 'CheckoutApp',
debugId: '0123456789ABCDEF0123456789ABCDEF0',
architecture: 'arm64',
instructionAddress: '0x1234567800000110',
imageAddress: '0x1234567800000000',
isReturnAddress: false,
inApp: true,
}],
});
Use the actual MODULE identifier and architecture, instruction address and image load base from the same build. Addresses are hexadecimal strings so 64-bit values retain precision. Set isReturnAddress for an unwound return PC that needs a one-byte adjustment; leave it false for the faulting instruction. Normalize architecture names to the symbol file's MODULE field. JavaScript custom frames instead need one-based line and column values.
Native resolution supports MODULE, FILE, FUNC, line and PUBLIC records. Functions take precedence; PUBLIC resolution stops at the next known symbol, and the final PUBLIC record resolves only its exact address. Overlapping function/line ranges are rejected. Symbol files are capped at 5 MiB and 100,000 records. STACK programs and INLINE metadata are ignored: this workflow resolves supplied physical frames, not raw crash dumps or expanded inline call stacks. Convert dSYM/PDB/ELF debug information before upload; binaries are not accepted. The optional @saaspro/native-crashes add-on supplies bounded automatic iOS/Android physical frames after separate consent. Custom host bridges remain supported; this artifact endpoint never accepts raw dumps.
Storage and troubleshooting
All environments share 500 files / 256 MiB uncompressed per application, across both artifact formats. Storage is compressed in PostgreSQL. Uploading requires errors.write; deletion additionally requires a reason and, in the console, a passkey verified within ten minutes. Changes and audit records commit together. Reusing an identity with identical content is idempotent; different content returns a conflict. Metadata listing never returns source or symbol content. Authorized error readers can see resolved source excerpts.
Each opened event resolves at most 80 frames, 24 artifacts and 10 MiB of artifact content. Its original stored frames and stack remain unchanged. Per-frame diagnostics distinguish:
- Missing artifact: check environment and exact debug ID; for legacy maps, check release and URL/path.
- Invalid position: supply one-based JavaScript line/column, or the native module, architecture and addresses.
- No mapping: the build matches, but its map has no source location for that position.
- Processing limit: reduce the number or size of loaded artifacts.
- Unreadable artifact: delete and upload the matching build again.
Deleting an artifact removes its resolved source locations from subsequent event reads. It does not delete errors. Retain artifacts as long as those errors need investigation.
Management API
Use the following paths with ?env=production, staging or development:
/api/v1/apps/{app}/errors/source-maps
/api/v1/apps/{app}/errors/native-symbols
GET returns metadata. POST accepts { sourceMap, debugId?, release?, bundle? } for JavaScript or { symbols } for native text. DELETE at the corresponding /{id} path accepts { reason } of 10–500 characters. Read/write scope and application membership are checked for every call. Public and secret ingest keys cannot access these Management API endpoints.