# Source maps & releases > https://docs.errora.org/en/products/source-maps Without source maps, a browser stack trace is `app.4f2c.js:1:28461`. With them, it is `src/checkout/pay.ts:88`, with the surrounding lines shown. Errora symbolicates **before grouping**, so issues group on stable original frames rather than on bundle hashes that change every deploy. ## 1. Emit source maps ```javascript // vite.config.ts export default defineConfig({ build: { sourcemap: true } }); ``` ```javascript // webpack.config.js module.exports = { devtool: "source-map" }; ``` Next.js: `productionBrowserSourceMaps: true` in `next.config.js`. ## 2. Set a release in the SDK The release string is the key everything is filed under. It must match **exactly** between the SDK and the upload. ```javascript Sentry.init({ dsn: "…", release: "web@1.4.2", // same string as the upload below dist: "prod", // optional, for platforms that build per-distribution }); ``` ## 3. Upload the artifacts from CI ```bash for map in dist/assets/*.js.map; do curl -sf -X POST "$ERRORA/api/v1/projects/$PROJECT_ID/release-files" \ -H "Authorization: Bearer $ERRORA_TOKEN" \ -F "release=web@1.4.2" \ -F "name=~/assets/$(basename "${map%.map}")" \ -F "sourcemap=1" \ -F "file=@$map" done ``` | Field | Meaning | | --- | --- | | `release` | Required. Must equal the SDK's `release`. | | `name` | Required. The artifact's URL path. `~/` stands for "any host", which is what you want unless you serve from one fixed origin. | | `dist` | Optional distribution label; must match the SDK's `dist`. | | `sourcemap` | Mark the upload as a map rather than a bundle. | | `file` | The file itself, `multipart/form-data`. | Uploading the same `(release, dist, name)` again replaces it, so re-running a deploy is safe. Use a [personal access token](/en/account/tokens) for CI — never a user's session token. ### GitLab CI example ```yaml upload-sourcemaps: stage: deploy script: - npm run build - | for map in dist/assets/*.js.map; do curl -sf -X POST "$ERRORA/api/v1/projects/$PROJECT_ID/release-files" \ -H "Authorization: Bearer $ERRORA_TOKEN" \ -F "release=web@$CI_COMMIT_SHORT_SHA" \ -F "name=~/assets/$(basename "${map%.map}")" \ -F "sourcemap=1" -F "file=@$map" done ``` …with `release: \`web@${process.env.CI_COMMIT_SHORT_SHA}\`` in the SDK init. ## 4. Verify Trigger an error in the deployed build and open the issue. Frames should show your original file, line, column and source context. The artifacts you uploaded are listed at: ```text GET /api/v1/projects/{project_id}/release-files?release=web@1.4.2&dist= POST /api/v1/projects/{project_id}/release-files GET /api/v1/projects/{project_id}/release-files/{id} DELETE /api/v1/projects/{project_id}/release-files/{id} ``` ## How it works For each minified frame, Errora finds the artifact by name, follows the map to the original file, line and column, and attaches the surrounding source lines when the map contains `sourcesContent`. Keep `sourcesContent` in your maps: without it you get the right filename and line, but no code context. ## Should the maps be public? No. Upload them to Errora and **do not** deploy `.map` files to your web server (or serve them behind auth). Errora reads them server-side; your users never need them. ## Troubleshooting | Symptom | Cause / fix | | --- | --- | | Frames still minified | `release` mismatch between SDK and upload — the most common cause by far | | Right file, no source context | The map has no `sourcesContent` | | Some frames resolved, some not | Only some bundles were uploaded; loop over *all* maps, including vendor chunks | | Wrong lines | The uploaded map belongs to a different build than the deployed bundle — upload from the same CI job that built it | | `401` on upload | The token is missing, or lacks access to that project |