Make stack frames readable.
GAThree upload paths — JavaScript source maps, Android ProGuard mappings, iOS dSYMs. All three are keyed by (project, release, filename or UUID) and used to de-obfuscate your stack frames.
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'Symbolication runs once per issue group on first creation. Frames are cached in an LRU keyed by release. The dashboard shows a small symbolicated badge on resolved frames.
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'The parser is pure-JS (no JVM). When a frame's function looks Android-shaped (short class.method tokens), the dispatcher routes through ProGuard; otherwise through SourceMapConsumer. Line-range entries are picked first; first-entry fallback otherwise.
iOS dSYM
Upload the inner DWARF Mach-O — not the .dSYM bundle:
# Inside the bundle
# YourApp.app.dSYM/Contents/Resources/DWARF/YourApp
curl -X POST 'https://tilldev.dev/api/projects/<id>/dsyms' \
-H 'Authorization: Bearer tp_9f3a...' \
-F 'file=@YourApp.app.dSYM/Contents/Resources/DWARF/YourApp' \
-F 'release=1.4.0' \
-F 'filename=YourApp'TillPulse parses the Mach-O LC_UUID load command (thin and fat binaries supported) and indexes each slice by its UUID. Crash images carry their image UUIDs; the resolver matches incoming frames to the right slice.
atos / dwarfdump) — the dSYM is stored and indexed, and frames render in the dashboard with the binary slice annotated. Full in-cluster lookup is on the roadmap.CLI
For CI pipelines, use the @tillstack/cli package. Login once with tilldev login; the token is stored at ~/.tilldev/config.json (mode 0600).
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 YourAppXcode build phase (recommended)
Add a Run Script phase after Archive:
if [ -n "$DWARF_DSYM_FOLDER_PATH" ]; then
for path in "$DWARF_DSYM_FOLDER_PATH"/*.dSYM; do
name=$(basename "$path" .app.dSYM)
tilldev pulse dsym upload "$TILLDEV_PROJECT" \
--release "$MARKETING_VERSION" \
--file "$path/Contents/Resources/DWARF/$name"
done
fiGitHub 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/:mapIdStorage
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.