Skip to content

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.

Three things have to line up, and this is where almost every failed setup goes wrong:

  1. Your SDK sends a version with each error.
  2. A release exists with that exact version string.
  3. A source map is uploaded under that release with a filename equal 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.

SiteQwalityRUM.init({
applicationId: 'abc123',
clientToken: 'pub_def456',
version: '1.4.2', // or process.env.GIT_SHA, or a release tag
});
Terminal window
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.

Uploading is two steps: ask for an upload URL, then send the file to it.

  1. 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
    }
    }
  2. 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.map

    The Content-Type must match what you asked for. If you omit content_type in step 1 it defaults to application/json, which is correct for a .map file.

Re-uploading the same filename to the same release replaces the previous map.

.github/workflows/deploy.yml
- 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"
done

Make sure your init({ version }) uses the same $GITHUB_SHA.

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.

Common causes, roughly in order of likelihood:

SymptomCause
Nothing is symbolicatedThe SDK is not sending version, or it does not match the release exactly.
Nothing is symbolicatedfilename was the .map name, or a full URL that does not match the frame. Use the bundle basename.
Nothing is symbolicatedThe upload URL expired before the PUT ran, so the record exists but the file does not.
Some frames resolve, others do notNormal for a stack spanning several bundles. Upload a map for each.
Frames from Firefox or Safari users stay minifiedFrame 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.

Terminal window
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.

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.