Upload source maps
Without source maps, a production error stack looks like this:
TypeError: Cannot read properties of undefined (reading 'name') at v (https://app.example.com/assets/main.4f9a3.js:1:42089) at d (https://app.example.com/assets/main.4f9a3.js:1:38215)Useless. With them, the same error is stored as:
TypeError: Cannot read properties of undefined (reading 'name') at renderProfile (src/components/UserProfile.tsx:42:18) at App (src/App.tsx:78:5)Source maps are included on every plan, including free.
How the matching works
Section titled “How the matching works”Three things have to line up, and this is where almost every failed setup goes wrong:
- Your SDK sends a
versionwith each error. - A release exists with that exact version string.
- A source map is uploaded under that release with a
filenameequal to the minified bundle name that appears in the stack trace.
That third one is the trap. filename is not the name of the .map file. It is the name of the JavaScript bundle the error came from.
When resolving a frame, SiteQwality tries the frame’s file reference verbatim, then its basename with any directory, query string and fragment stripped, then that basename with .map appended. So for a frame pointing at https://app.example.com/assets/main.4f9a3.js, uploading with filename: "main.4f9a3.js" is the reliable choice.
1. Tag your bundle with a version
Section titled “1. Tag your bundle with a version”SiteQwalityRUM.init({ applicationId: 'abc123', clientToken: 'pub_def456', version: '1.4.2', // or process.env.GIT_SHA, or a release tag});2. Create the release
Section titled “2. Create the release”curl -X POST "https://api.siteqwality.com/rum/$APP_ID/releases" \ -H "Authorization: Bearer $SITEQWALITY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "version": "1.4.2" }'version is required, trimmed, and at most 255 characters. Uploading to a release that does not exist returns 404, so this step comes first.
3. Upload each map
Section titled “3. Upload each map”Uploading is two steps: ask for an upload URL, then send the file to it.
-
Request an upload URL, naming the minified bundle:
Terminal window curl -X POST "https://api.siteqwality.com/rum/$APP_ID/releases/1.4.2/sourcemaps" \-H "Authorization: Bearer $SITEQWALITY_API_KEY" \-H "Content-Type: application/json" \-d '{ "filename": "main.4f9a3.js" }'The response gives you an upload URL and how long it is valid for:
{"data": {"upload_url": "https://...","s3_key": "abc123/1.4.2/main.4f9a3.js","expires_in_seconds": 300}} -
PUT the map file to that URL within 5 minutes:
Terminal window curl -X PUT "$UPLOAD_URL" \-H "Content-Type: application/json" \--data-binary @dist/assets/main.4f9a3.js.mapThe
Content-Typemust match what you asked for. If you omitcontent_typein step 1 it defaults toapplication/json, which is correct for a.mapfile.
Re-uploading the same filename to the same release replaces the previous map.
Automate in CI
Section titled “Automate in CI”- name: Build run: npm run build
- name: Create RUM release run: | curl -fsS -X POST "https://api.siteqwality.com/rum/$APP_ID/releases" \ -H "Authorization: Bearer ${{ secrets.SITEQWALITY_API_KEY }}" \ -H "Content-Type: application/json" \ -d "{\"version\":\"$GITHUB_SHA\"}"
- name: Upload source maps run: | for map in dist/assets/*.js.map; do bundle=$(basename "$map" .map) url=$(curl -fsS -X POST \ "https://api.siteqwality.com/rum/$APP_ID/releases/$GITHUB_SHA/sourcemaps" \ -H "Authorization: Bearer ${{ secrets.SITEQWALITY_API_KEY }}" \ -H "Content-Type: application/json" \ -d "{\"filename\":\"$bundle\"}" | jq -r '.data.upload_url') curl -fsS -X PUT "$url" \ -H "Content-Type: application/json" \ --data-binary "@$map" doneMake sure your init({ version }) uses the same $GITHUB_SHA.
Symbolication happens once, at ingest
Section titled “Symbolication happens once, at ingest”This is the most important behavioural detail, and it has real consequences.
Errors are symbolicated when they arrive, not when you view them. That means:
- Uploading a map does not fix errors that already arrived. Errors ingested before the map existed keep their minified stack permanently. There is no re-processing.
- Upload maps as part of the deploy that ships the bundle, before real traffic produces errors. A map uploaded an hour late leaves an hour of unreadable errors.
- Your error groups will split at the cutover. Errors are grouped by a fingerprint computed over the stack, so the same bug appears as one group before symbolication starts working and a different group afterwards. Expect this once, the first time you set it up.
When it does not work
Section titled “When it does not work”Common causes, roughly in order of likelihood:
| Symptom | Cause |
|---|---|
| Nothing is symbolicated | The SDK is not sending version, or it does not match the release exactly. |
| Nothing is symbolicated | filename was the .map name, or a full URL that does not match the frame. Use the bundle basename. |
| Nothing is symbolicated | The upload URL expired before the PUT ran, so the record exists but the file does not. |
| Some frames resolve, others do not | Normal for a stack spanning several bundles. Upload a map for each. |
| Frames from Firefox or Safari users stay minified | Frame parsing targets V8-style stack syntax, which is what Chromium and Node produce. Other engines’ formats pass through unchanged. |
There is no endpoint for listing the maps you have uploaded, so when debugging a setup, re-uploading is cheaper than trying to inspect state.
Cleanup
Section titled “Cleanup”curl -X DELETE "https://api.siteqwality.com/rum/$APP_ID/releases/0.9.1" \ -H "Authorization: Bearer $SITEQWALITY_API_KEY"This removes the release and its map records. The stored map files themselves are not deleted, and there is no limit on how many releases or maps you can keep, so this is housekeeping rather than a necessity.
Don’t ship source maps publicly
Section titled “Don’t ship source maps publicly”Your bundler probably writes //# sourceMappingURL=main.4f9a3.js.map at the bottom of each bundle. If your pipeline also publishes the .map files, anyone can de-minify your code.
To keep maps private (uploaded to SiteQwality, not published):
- Vite:
build.sourcemap = 'hidden' - Webpack:
devtool: 'hidden-source-map' - Rollup:
output.sourcemap = 'hidden'
These generate the maps without writing the sourceMappingURL comment. Browsers won’t fetch them; SiteQwality has them via the upload.