Production builds are minified, so a captured exception's stack trace points at mangled names and bundled line/column numbers instead of your original source. Upload the sourcemaps your build already produces and the dashboard decodes every frame back to original file, line, column, and function name automatically — no change needed on the Exception/Issue pages themselves, they just start rendering resolved frames once a match exists.
Resolution keys off two things matching exactly: the release stamped on the exception, and the filename of the minified file the stack frame points at. Get either wrong and the frame is silently left unresolved rather than treated as an error — there's no error state to debug, just a frame that still shows raw/minified.
Set a release on the SDK
release is a plain string you control — a git SHA, a semver tag, a build number, anything that uniquely identifies one deploy. Set it wherever you call init():
ts
Buglapse.init({
dsn: '{dsn}',
release: '1.4.2',
})or on the CDN script tag:
html
<script src="https://app.buglapse.com/cdn/{project-token}/buglapse.min.js" data-release="1.4.2" async></script>It's stamped as contexts.release on the base context and, for exceptions only, lifted into its own release column at ingestion — that's the value matched against uploads below. Whatever value you pick, use the exact same one for the sourcemap upload of that build (see next section) — a mismatch of even one character means nothing resolves.
Upload the maps
Uploads go to the management API, POST /api/0/projects/{organization}/{project}/sourcemaps, where {organization} and {project} are the slugs from the project's dashboard URL (https://app.buglapse.com/{organization}/{project}/…). They're authenticated with a personal access token carrying the project:releases scope — create one on Profile → Tokens and send it as Authorization: Bearer <token>. It's separate from the DSN: never put it in client-side code, it can write data and only belongs in a build/CI secret. The organization's plan must include source maps, otherwise uploads are rejected with 403.
@buglapse/vite-plugin (recommended)
For a Vite build, the plugin wires up sourcemap generation and upload as part of the build itself — no separate CI step to maintain. Like the SDK packages, it's installed from Buglapse's own registry (@buglapse:registry=https://app.buglapse.com/npm/ in .npmrc):
bash
npm install --save-dev @buglapse/vite-plugints
// vite.config.ts
import { defineConfig } from 'vite'
import { sourcemaps } from '@buglapse/vite-plugin'
export default defineConfig({
plugins: [
sourcemaps({
url: 'https://app.buglapse.com',
authToken: process.env.BUGLAPSE_AUTH_TOKEN, // personal access token, scope project:releases
org: '{organization}',
project: '{project}',
release: process.env.BUILD_RELEASE, // must match the `release` passed to init()
}),
],
})It sets build.sourcemap = 'hidden' for you (maps are still emitted so they can be uploaded, but without a //# sourceMappingURL comment pointing at them), uploads every emitted .map file after the bundle is generated, and then strips the .map files back out of the build output — so a sourcemap that would otherwise expose your original source (most maps embed it via sourcesContent) never ships to dist/. Set deleteAfterUpload: false to keep them in the output instead, or ssr: false to skip uploading (and keep stripping) sourcemaps from an SSR/ server build you don't need resolved. A failed upload logs a warning per file and doesn't fail the build.
Manual upload (any build tool)
No Vite, or want to script it yourself — curl the same endpoint directly, once per emitted file, as part of your deploy/CI step, after the build has produced .map files:
bash
curl -X POST "https://app.buglapse.com/api/0/projects/{organization}/{project}/sourcemaps" \
-H "Authorization: Bearer $BUGLAPSE_AUTH_TOKEN" \
-F "release=$BUILD_RELEASE" \
-F "filename=assets/app.a1b2c3.js" \
-F "map=@dist/assets/app.a1b2c3.js.map"release— must match the SDK'sreleasefor this build (above).filename— the path of the minified file, exactly as it appears in the browser's stack trace URL, not the.mapfile. A leading/or./is normalized away server-side, soassets/app.js,/assets/app.js, and./assets/app.jsall resolve the same way — but the rest of the path (including any subdirectory) has to match the stack frame's URL path verbatim.map— the sourcemap file itself, as multipart form data. Must be valid JSON with amappingskey (a real sourcemap) — anything else is rejected with a 422, as is a file over the 50MB size limit.
Re-uploading the same (release, filename) pair — a re-run of the same build, a hotfix to the same release — replaces the existing row rather than erroring or duplicating it.
How resolution works
The dashboard's stack trace view looks up each frame by release + the frame's file path (the path part of its URL), decodes the stored map on the server, and swaps in the original file/line/column/function name. A frame from a file with no matching upload — a vendor chunk you didn't bundle sourcemaps for, a release that was never uploaded, a typo in either value — is just left unresolved; it's not surfaced as an error anywhere.
The browser only ever receives the decoded frames, never the map itself. A sourcemap routinely embeds your entire original source under sourcesContent, so keeping it on the server is the whole point of uploading through this authenticated endpoint instead of just deploying the .map files alongside your bundle.
Describe the release from CI (optional)
Every release seen on an exception or a source map upload gets a row on the project's Releases page automatically. To enrich it with where the build came from, call POST /api/0/projects/{organization}/{project}/releases from the same CI step, with the same token as the sourcemap upload:
bash
curl -X POST "https://app.buglapse.com/api/0/projects/{organization}/{project}/releases" \
-H "Authorization: Bearer $BUGLAPSE_AUTH_TOKEN" \
-F "version=$BUILD_RELEASE" \
-F "ref=$GIT_SHA" \
-F "url=https://github.com/acme/app/commit/$GIT_SHA" \
-F "deployed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"version(required) — the same string as the SDK'srelease.ref,url(anhttp(s)link to the build, deploy or commit),deployed_at,notes— all optional. Only the fields you send are written, so a later call with justdeployed_atdoesn't blank arefset by an earlier one.
Managing uploads
Uploaded maps are managed per release on the Releases page: open a release to see every file uploaded under it (with size and upload time), delete a single file, or delete all of the release's maps at once (e.g. to drop an old release you no longer need resolved). The release's page also shows its exception count, the issues first seen in it, and its first/last seen times. Deleting a release's maps doesn't touch already-captured exceptions — it just means their frames stop resolving until re-uploaded.