dSYM Upload
Upload your app's dSYM files to Pulse so crash stack traces are symbolicated (human-readable) in the dashboard.
The script pulse-upload-dsym.sh is bundled inside PulseKit.framework — no separate install needed.
Xcode Build Phase (recommended)
This runs automatically on every archive/release build.
- Open your app target → Build Phases → + → New Run Script Phase
- Drag it to run after "Embed Frameworks"
- Set shell to
/bin/bashand paste:
UPLOAD_SCRIPT="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/PulseKit.framework/pulse-upload-dsym.sh"
# CocoaPods fallback for archive builds
if [[ ! -f "${UPLOAD_SCRIPT}" && -n "${PODS_XCFRAMEWORKS_BUILD_DIR:-}" ]]; then
UPLOAD_SCRIPT="${PODS_XCFRAMEWORKS_BUILD_DIR}/PulseKit/PulseKit.framework/pulse-upload-dsym.sh"
fi
bash "${UPLOAD_SCRIPT}" \
--url="https://YOUR_HOST/v1/symbolicate/file/upload" \
--api-key="${PULSE_API_KEY}" \
--dsym="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}" \
--app-version="${MARKETING_VERSION}" \
--version-code="${CURRENT_PROJECT_VERSION}" \
--type=dsym
Replace YOUR_HOST with your Pulse backend URL. Set PULSE_API_KEY as an Xcode scheme environment variable or CI secret — don't commit it.
Skip uploads on Debug builds (optional — add before the script block):
if [[ "${CONFIGURATION}" != "Release" ]]; then exit 0; fi
Manual Upload (one-off or CI)
If you already have a .dSYM on disk:
1. Find the script (after building locally):
find ~/Library/Developer/Xcode/DerivedData \
-path "*PulseKit.framework/pulse-upload-dsym.sh" 2>/dev/null \
| grep -v Index.noindex | head -5
2. Run it:
bash "/path/to/PulseKit.framework/pulse-upload-dsym.sh" \
--url="https://YOUR_HOST/v1/symbolicate/file/upload" \
--api-key="YOUR_API_KEY" \
--dsym="/path/to/YourApp.app.dSYM" \
--app-version="1.0.0" \
--version-code="123" \
--type=dsym
CI/CD
The Xcode build phase handles uploads automatically when your CI runs xcodebuild archive. If you prefer to upload separately as a post-build step, here are patterns for the most common CI tools.
The dSYM path after xcodebuild archive is inside the .xcarchive:
MyApp.xcarchive/dSYMs/YourApp.app.dSYM
GitHub Actions
- name: Upload dSYMs to Pulse
run: |
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/PulseKit.framework/pulse-upload-dsym.sh" \
--url="https://YOUR_HOST/v1/symbolicate/file/upload" \
--api-key="${{ secrets.PULSE_API_KEY }}" \
--dsym="MyApp.xcarchive/dSYMs/YourApp.app.dSYM" \
--app-version="${{ env.APP_VERSION }}" \
--version-code="${{ env.BUILD_NUMBER }}" \
--type=dsym
Store your API key in Settings → Secrets → Actions as PULSE_API_KEY.
Fastlane
Add a lane or append to your existing release lane:
lane :upload_dsyms do
sh(
"bash", "#{ENV['BUILT_PRODUCTS_DIR']}/#{ENV['FRAMEWORKS_FOLDER_PATH']}/PulseKit.framework/pulse-upload-dsym.sh",
"--url=https://YOUR_HOST/v1/symbolicate/file/upload",
"--api-key=#{ENV['PULSE_API_KEY']}",
"--dsym=#{lane_context[SharedValues::DSYM_OUTPUT_PATH]}",
"--app-version=#{get_version_number}",
"--version-code=#{get_build_number}",
"--type=dsym"
)
end
Call it after gym (which sets SharedValues::DSYM_OUTPUT_PATH automatically).
Bitrise
Add a Script step after your Xcode Archive step:
#!/usr/bin/env bash
set -euo pipefail
bash "${BITRISE_FRAMEWORKS_FOLDER_PATH}/PulseKit.framework/pulse-upload-dsym.sh" \
--url="https://YOUR_HOST/v1/symbolicate/file/upload" \
--api-key="${PULSE_API_KEY}" \
--dsym="${BITRISE_DSYM_PATH}" \
--app-version="${BITRISE_APP_VERSION}" \
--version-code="${BITRISE_BUILD_NUMBER}" \
--type=dsym
Set PULSE_API_KEY as a Secret env var in your Bitrise workflow.
Troubleshooting
| Problem | Fix |
|---|---|
| Script not found | Build the app target first so PulseKit.framework is embedded in the .app |
| Script missing during archive (CocoaPods) | The PODS_XCFRAMEWORKS_BUILD_DIR fallback in the build phase script handles this |
| Permission or shell errors | Use bash … not sh … |
Next Steps
- API Reference — Browse all public methods