Guides / Upload dSYMsMenu

Upload dSYMs

Name crash stacks in Tapll by uploading each build’s dSYMs from Xcode Cloud, fastlane or an Xcode build phase.

Updated · Markdown · Ask Claude · Ask ChatGPT

Crash stacks arrive as addresses. Upload each build’s dSYMs from CI and Tapll names every frame of your own code — in the stack, in the issue title, in alerts.

What an upload does

The Tapll SDK sends each frame as a binary UUID and an offset. With the dSYM for that UUID, Tapll shows the function instead: CheckoutViewModel.submit() + 44 rather than MyApp +0x223f4. Issues that already exist are renamed when their dSYM arrives, so an upload that lands a day after the crash still helps.

An upload does not merge issues across builds. Tapll groups a crash by where it happened in one build, and that stays true with names — so an issue still covers one build, and a rebuilt app with the same bug opens a new one. Function names today; file and line come later from the same upload, without asking you to upload again.

Get a server key

In your project, open Settings → Server keys and create a key (sk_…). Store it in your CI’s secret store as TAPLL_SERVER_KEY.

The app’s client key is refused The key in your app build (av_…) ships inside every copy of the app. Accepting it here would let anyone who unpacked your IPA rename your stack traces, so uploads need a server key.

Xcode Cloud

Add ci_scripts/ci_post_xcodebuild.sh next to your project and make it executable (chmod +x). Add TAPLL_SERVER_KEY to the workflow as a secret environment variable. Runs after every archive action.

#!/bin/sh
set -e
if [ "$CI_XCODEBUILD_ACTION" = "archive" ]; then
  curl -fsSL https://tapll.com/dsyms/v3.sh -o "/tmp/tapll-dsyms.sh" \
    && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d  /tmp/tapll-dsyms.sh" | shasum -a 256 -c - \
    && bash "/tmp/tapll-dsyms.sh" "$CI_ARCHIVE_PATH"
fi

fastlane

After build_app (gym), which leaves the zipped dSYMs at DSYM_OUTPUT_PATH. Set TAPLL_SERVER_KEY in your CI secrets.

lane :release do
  build_app(scheme: "MyApp")
  sh(<<~SH)
    curl -fsSL https://tapll.com/dsyms/v3.sh -o "/tmp/tapll-dsyms.sh" \
      && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d  /tmp/tapll-dsyms.sh" | shasum -a 256 -c - \
      && bash "/tmp/tapll-dsyms.sh" "#{lane_context[SharedValues::DSYM_OUTPUT_PATH]}"
  SH
  upload_to_testflight
end

Xcode build phase

Add a Run Script phase as the last phase of the app target, with ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME} as an input file. Release builds only. Turn off User Script Sandboxing for the target (the script reads the dSYM and uses the network), and provide TAPLL_SERVER_KEY from the environment — never paste it into the project.

[ "$CONFIGURATION" = "Release" ] || exit 0
curl -fsSL https://tapll.com/dsyms/v3.sh -o "$DERIVED_FILE_DIR/tapll-dsyms.sh" \
  && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d  $DERIVED_FILE_DIR/tapll-dsyms.sh" | shasum -a 256 -c - \
  && bash "$DERIVED_FILE_DIR/tapll-dsyms.sh" "$DWARF_DSYM_FOLDER_PATH"

By hand

For a build that shipped before you set this up, run the script on the archive yourself. Xcode keeps archives in Window → Organizer; right-click one and choose Show in Finder.

TAPLL_SERVER_KEY=sk_your_server_key \
  bash v2.sh ~/Library/Developer/Xcode/Archives/2026-09-28/MyApp.xcarchive

Or let the script find it. Settings → Debug symbols → Missing dSYMs lists the binary UUIDs your crashes are in that no upload names yet, and gives one command carrying all of them. With --uuid and no path, the script looks each one up through Spotlight, then in Xcode’s Archives and DerivedData, uploads what it finds and names what it could not. With a path, it uploads only the dSYMs in it that hold those UUIDs.

TAPLL_SERVER_KEY=sk_your_server_key \
  bash v2.sh --uuid 6474C08F92D03410BFAA668957E3F700

A lost dSYM cannot be rebuilt Only the dSYM of the exact build that shipped matches. Rebuilding the same commit produces new UUIDs, so a dSYM that is gone cannot be recreated — crashes in that build keep their addresses.

The script

https://tapll.com/dsyms/v3.sh runs on the Mac that built your app. For each binary in each dSYM it reads the function table with Apple’s own tools (dwarfdump, nm, swift-demangle), uploads it, and archives the dSYM itself. Binaries Tapll already has are skipped, so running it on every build costs one request per binary.

The file is versioned: v2.sh never changes behaviour, so what runs in your pipeline is what you reviewed (v1.sh stays published for pipelines that pinned it; v2 adds --uuid and is otherwise the same). Every snippet above checks it against its SHA-256 before running it:

45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d
Option What it does
--uuid Upload only the dSYMs holding this binary UUID (repeatable). Without a path, find them on this Mac.
--no-raw Upload the function tables only, not the dSYM itself. File and line will not be available for these builds later.
--strict Exit non-zero when an upload fails. Without it, a failure is a warning and never fails your build.
--dry-run Extract and write the tables to the current folder without uploading. Needs no key.

Check it worked

Settings → Debug symbols lists every binary Tapll has, with its UUID. A crash is named only when its binary carries exactly that UUID — a dSYM from a different build of the same version will not match, and the crash page says which UUID it is waiting for.