Try on device runs your app on a live device inside the dashboard: an iOS or tvOS simulator, an Android or Android TV emulator, or a Roku. Open a report or a crash, choose Try on device, and Everframe installs the build that best matches the report, streams the device to your browser, and shows the reproduction steps beside it. You drive the device with your mouse and keyboard. Nothing installs on your machine.
It needs one thing from you: a runnable build uploaded from CI. Source maps and native symbols map crash frames, but they cannot launch an app, so Try on device uses a separate upload with the same credentials.
Supported targets
| SDK | Runnable targets |
|---|---|
| iOS | iOS, tvOS |
| Android | Android phone and tablet, Android TV |
| React Native (incl. Expo) | iOS, tvOS, Android, Android TV |
| Flutter, KMP | iOS, Android |
| Roku | Roku |
| React, Web, Vega OS | Not supported; there is no device app. |
Each target takes one package type:
--platform | Package |
|---|---|
android, android-tv | APK |
ios, tvos | Simulator .app, ZIPped with the bundle at its root |
roku | Roku sideload ZIP |
IPA and AAB store packages are refused. An App Store or TestFlight archive is built for physical devices and does not contain a simulator app; build an APK for Android, and a separate simulator build for iOS and tvOS.
Upload a runnable build
The upload is a CI step with the Everframe CLI.
It uses the same masked EVERFRAME_API_TOKEN as your symbol uploads: an API
token with artifacts:write, shown as Upload source maps in
Settings → API tokens. See
Use the right credentials. Never
ship this token in an app.
npm install --global @everframe/cli
export EVERFRAME_API_TOKEN='<masked artifacts:write token>' # CI secret
export EVERFRAME_APP_ID='<app UUID>' # the App ID on the app's Setup tab
The fastest start is the app’s Setup tab in the dashboard. Under Enable Try on device (optional), pick the target and enter your package path and the names of your CI version variables. It generates the exact upload and status commands for your project, ready to paste into CI.
Pass the same version and build number your SDK reports, as strings, so
0042 stays 0042. They are how Everframe finds the right build for a report.
--commit <full-sha> is optional: without it, the CLI reads the commit from Git
or your CI environment, and uploads without one when neither is available.
Run your APK-producing Gradle task, then upload its output. Use
--platform android-tv for an Android TV APK.
./gradlew <APK-assemble-task>
everframe device-builds upload --platform android \
--file "<path/to/app.apk>" \
--version "$APP_VERSION" --build-number "$APP_BUILD_NUMBER" \
--wait --json
Build for the simulator, then ZIP the .app with ditto so the bundle sits at
the archive root. Use -workspace instead of -project if your app needs one,
and add any build options your CI already passes.
xcodebuild -project "<path/to/App.xcodeproj>" -scheme "<iOS-scheme>" \
-sdk iphonesimulator -destination 'generic/platform=iOS Simulator' \
-derivedDataPath "<path/to/derived-data>" build
ditto -c -k --sequesterRsrc --keepParent \
"<path/to/built/App.app>" "<path/to/ios-simulator.zip>"
everframe device-builds upload --platform ios \
--file "<path/to/ios-simulator.zip>" \
--version "$APP_VERSION" --build-number "$APP_BUILD_NUMBER" \
--wait --json
Same as iOS, with your tvOS scheme and the Apple TV simulator.
xcodebuild -project "<path/to/App.xcodeproj>" -scheme "<tvOS-scheme>" \
-sdk appletvsimulator -destination 'generic/platform=tvOS Simulator' \
-derivedDataPath "<path/to/derived-data>" build
ditto -c -k --sequesterRsrc --keepParent \
"<path/to/built/AppTV.app>" "<path/to/tvos-simulator.zip>"
everframe device-builds upload --platform tvos \
--file "<path/to/tvos-simulator.zip>" \
--version "$APP_VERSION" --build-number "$APP_BUILD_NUMBER" \
--wait --json
Upload the sideload ZIP your packaging step already produces.
everframe device-builds upload --platform roku \
--file "<path/to/roku-sideload.zip>" \
--version "$APP_VERSION" --build-number "$APP_BUILD_NUMBER" \
--wait --json
Everframe checks the package contents after upload, not just the filename, then prepares the build. Files may be up to 500 MiB. Your local file is left in place.
Wait for ready, or check later
An accepted upload is not yet a ready build. Without --wait, the command
returns as soon as the upload is accepted and prints its upload ID and state,
such as queued. With --wait, it returns when the build is ready, failed
or expired. --timeout <seconds> caps the whole command, upload and retries
included; the default is 600.
A timeout after acceptance does not mean the build failed. The CLI prints the upload ID and a status command. Check that before uploading again:
everframe device-builds status --app-id "$EVERFRAME_APP_ID" \
--upload-id "<upload-uuid>" --json
| Result | Exit code |
|---|---|
Upload accepted (no --wait), or build ready (with --wait) | 0 |
Upload rejected, or build failed / expired | 1 |
--wait deadline reached after the upload was accepted | 2 |
status: receiving, queued, uploading, retrying or ready | 0 |
status: request error, or build failed / expired | 1 |
--json prints one JSON object on stdout with uploadId, state and, once
ready, buildId. Errors add error.code and error.message, and the upload
ID, last known state and status command when known. Human-readable diagnostics
go to stderr.
Keep it separate from symbol uploads
A runnable build and your symbols are independent. A failed or slow device upload does not undo symbols that already uploaded, and should not fail the release by accident. Run it as its own step, and gate on its exit code only if your release requires a ready build:
symbols_exit=0
everframe dsym upload-build --archive "$XCODE_ARCHIVE_PATH" --strict || symbols_exit=$?
device_exit=0
everframe device-builds upload --platform ios \
--file "$IOS_SIMULATOR_ZIP" \
--version "$APP_VERSION" --build-number "$APP_BUILD_NUMBER" \
--wait --timeout 600 || device_exit=$?
if [ "$device_exit" -ne 0 ]; then
printf 'Device upload exited %s; check its status with the upload ID above.\n' "$device_exit" >&2
fi
exit "$symbols_exit"
How Everframe picks the build
When you open a report, Everframe looks at the ready builds for the same app and target, and picks one in this order:
- Matching commit: the build uploaded from the commit the report came from.
- Matching version and build: the same version and build number the report carries.
- Latest ready build: when nothing matches, the newest one.
Before you start, the panel says which rule matched, for example Report is from 2.4.0 (0042); running 2.4.0 (0042). Matching version and build · 1 candidate. It flags a Build differs mismatch, and a device model or OS version that differs from the reporter’s. A different build may not reproduce the bug, so upload a build for every release you want to investigate.
Everframe keeps the five most recent ready builds per app and target. Older ones are removed automatically, except while a session is using them.
Start a session
- Open a report or a crash in the dashboard. The Try on device tab appears next to the screenshots and replay when the app has a ready build for that report’s target.
- Choose Try on device. Everframe prepares the session and shows the build, the device, and the reproduction steps. Nothing has started yet.
- Click Start. If every device is busy, the session waits in a queue; Cancel queued session gives up the place.
- Use the device directly. The controls beside it take a Screenshot, press Back (Android, Android TV and Roku), and Rotate phones and tablets. Drag the edge of the device view to resize it.
- Click End session when you are done. Switching back to the replay or a screenshot ends it too, and closing the browser tab ends it shortly after, so a forgotten session does not keep using time.
The header shows the session’s time remaining. A session ends on its own after 10 minutes; start another to keep going. If the reviewed build or device changes before you click Start, Everframe shows the new details and asks you to click Start again, so you never get a different device than the one you reviewed.
Device time
Device time is a monthly allowance per organization, set by your plan and shared by everyone in it. It resets at the start of each calendar month (UTC).
- Only time the app is actually running counts, rounded to the second.
- Waiting in the queue, starting up and failed starts are free.
- An organization runs one session at a time.
- When the allowance is used up, the panel says so until next month.
Privacy and isolation
Runnable builds are private to your organization. Each app and target keeps its own build storage on the device service, and a session installs exactly the build you reviewed. Every dashboard request checks your current project access, so removing someone from a project also stops them starting sessions on it.
When the tab is missing or Start is unavailable
| Message or symptom | What to do |
|---|---|
| No Try on device tab | Upload a runnable build for the report’s app and target, and wait until status says ready. Web reports never show the tab. |
| No ready runnable build is available for this report. | Same as above. A build that is queued or failed cannot run. |
| This report does not have a supported device platform. | The report came from a platform without a runnable target, such as the web. |
| No compatible simulator or emulator is available. | No device can run this target right now. Try again later. |
| All available device sessions are in use. Try again later. | Every device is busy. Wait and try again. |
| No device time remains in the current allowance. | Your organization used this month’s device time. It resets on the 1st (UTC). |
| The device selection changed. Review the details and click Start again. | A newer build or a different device was chosen since you opened the panel. Check the details and start again. |
| The device session has reached its time limit. | The 10-minute session ended. Close the panel and start a new session. |
Upload errors
The CLI exits 1 and prints error.code. A server refusal arrives as
request_failed with its HTTP status; the CLI retries 429 and 5xx
responses on its own, within --timeout.
error.code | What to do |
|---|---|
missing_api_token | Set EVERFRAME_API_TOKEN in the CI step. |
invalid_input / invalid_commit | Check --app-id is the app UUID, --timeout is a positive number, and --commit is a full 40- or 64-character SHA. |
invalid_format | Android and Android TV take an .apk; iOS and tvOS a ZIPped simulator .app; Roku a sideload .zip. IPA and AAB are refused. |
upload_too_large | The package is over 500 MiB. |
source_file_unavailable / source_file_changed | The file is missing, or changed while uploading. Run the upload after the build step has finished writing it. |
request_failed: HTTP 401 | Use a non-revoked API token. The public SDK ingest key is not accepted. |
request_failed: HTTP 403 | Give the token artifacts:write (Upload source maps); existing tokens do not gain it automatically. |
request_failed: HTTP 404 | Check the app UUID, the token’s project access, and the upload ID. |
request_timeout / upload_timeout | The network or --timeout ran out. If an upload ID was printed, check status before uploading again. |
wait_timeout (exit 2) | The upload was accepted and is still processing. Check status with the printed upload ID. |
A build that reaches failed reports why:
error.code | What to do |
|---|---|
invalid_format / invalid_content | The package did not pass inspection: for example a ZIP with no .app at its root, or a device-only binary. |
upload_too_large | The package is over the size limit. |
upload_expired | The upload was not completed in time. Start a new upload. |
provider_unavailable / retry_exhausted | The device service could not take the build. Upload again later. |
See Errors & limits for every limit in one place.