Make stack frames readable.
GAThree kinds of symbol file — JavaScript source maps, Android ProGuard mappings, iOS and macOS dSYMs. Upload them per release and TillPulse resolves every crash’s frames to function, file and line on the server, before the event is grouped into an issue.
JavaScript source maps
For React Native, the bundler emits a main.jsbundle.map alongside the bundle.
# Build the release bundle (RN ≥ 0.73)
npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output main.jsbundle \
--sourcemap-output main.jsbundle.map
# Upload
curl -X POST 'https://tilldev.dev/api/projects/<id>/source-maps' \
-H 'Authorization: Bearer tp_9f3a...' \
-F 'file=@main.jsbundle.map' \
-F 'release=1.4.0' \
-F 'filename=main.jsbundle.map' \
-F 'symbolication_type=js_source_map'Each error is symbolicated as it arrives, against the files uploaded for its release. Upload before you ship: errors that arrive before their symbols stay as reported.
Android ProGuard / R8
The ProGuard mapping file is at build/outputs/mapping/release/mapping.txt:
curl -X POST 'https://tilldev.dev/api/projects/<id>/source-maps' \
-H 'Authorization: Bearer tp_9f3a...' \
-F 'file=@app/build/outputs/mapping/release/mapping.txt' \
-F 'release=1.4.0' \
-F 'filename=mapping.txt' \
-F 'symbolication_type=proguard'Obfuscated class and method names in Java and Kotlin frames are restored, with line numbers where the mapping records them.
dSYMs
Native frames from iOS and macOS apps — Swift, Objective-C, C and C++, in React Native and Flutter apps alike — resolve to function, file and line, with inlined calls expanded and Swift and C++ names demangled. Each crash lists the build UUID of every loaded binary, and TillPulse matches those against the UUIDs of your uploaded dSYMs, so a dSYM applies to every crash from that exact build.
Xcode writes a .dSYM bundle per binary: one for the app and one for each framework you build. Upload them all, from the folder Xcode puts them in, an .xcarchive, or one bundle at a time:
# Every .dSYM under a folder
tilldev pulse dsym upload <id> --release 1.4.0 --file build/MyApp.xcarchive
# One bundle
tilldev pulse dsym upload <id> --release 1.4.0 --file MyApp.app.dSYMXcode writes dSYMs when Debug Information Format is DWARF with dSYM File, the default for Release builds.
Files up to 32 MB can also be sent in one request, as the DWARF file inside the bundle:
curl -X POST 'https://tilldev.dev/api/projects/<id>/dsyms' \
-H 'Authorization: Bearer tp_9f3a...' \
-F 'file=@MyApp.app.dSYM/Contents/Resources/DWARF/MyApp' \
-F 'release=1.4.0'Larger files, up to 1 GB, go up in parts, which the CLI and the dashboard do for you. Over the API: start an upload with POST /api/projects/:id/dsyms/uploads, send each part with PUT …/uploads/:uploadId/parts/:n, then POST …/uploads/:uploadId/complete with the part numbers and etags. See the API reference.
.dSYM bundle instead.CLI
For CI pipelines, use the @tillstack/cli package. Login once with tilldev login; the token is stored at ~/.tilldev/config.json (mode 0600). In CI, set TILLDEV_TOKEN to an API key instead.
tilldev login
tilldev pulse releases create pay 1.4.0 --env production
tilldev pulse sourcemaps upload pay --release 1.4.0 --file main.jsbundle.map
tilldev pulse proguard upload pay --release 1.4.0 --file mapping.txt
tilldev pulse dsym upload pay --release 1.4.0 --file build/MyApp.xcarchiveUpload on every archive
In Xcode, open Product ▸ Scheme ▸ Edit Scheme ▸ Archive ▸ Post-actions, add a New Run Script Action, set Provide build settings from to your app target, and paste:
export PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
tilldev pulse dsym upload "$TILLDEV_PROJECT" \
--release "$MARKETING_VERSION" \
--dist "$CURRENT_PROJECT_VERSION" \
--file "$ARCHIVE_DSYMS_PATH" > "$TMPDIR/tilldev-dsym.log" 2>&1Define TILLDEV_PROJECT as a user-defined build setting of the target. Post-action output doesn’t appear in the build log, hence the log file. In CI, upload from the archive after xcodebuild archive:
xcodebuild archive -scheme MyApp -archivePath build/MyApp.xcarchive
tilldev pulse dsym upload "$TILLDEV_PROJECT" --release 1.4.0 --file build/MyApp.xcarchiveGitHub Actions (Android)
- name: Upload ProGuard mapping
if: success()
run: |
npm i -g @tillstack/cli
tilldev pulse proguard upload "$\{\{ vars.TILLDEV_PROJECT }}" \
--release "$\{\{ github.sha }}" \
--file app/build/outputs/mapping/release/mapping.txt
env:
TILLDEV_TOKEN: $\{\{ secrets.TILLDEV_TOKEN }}Listing & deleting
GET /api/projects/:id/source-maps
GET /api/projects/:id/dsyms
DELETE /api/projects/:id/source-maps/:mapIdThe listing includes dSYMs; deleting one row of a multi-architecture dSYM removes only that architecture’s UUID.
Storage
Uploaded symbols are stored securely and retained per release — there's nothing for you to host or configure. Manage or delete uploads from the dashboard or via the listing and delete endpoints above.
When your workspace puts TillPulse under a customer key, every new upload is sealed in storage with your key and existing ones are sealed in the background. Symbolication keeps working while the key is reachable; if you lock the key, uploads are refused with 423 until it is back.