xcrun simctl is the command-line tool for the iOS Simulator: it boots devices, installs and launches apps, opens deep links, adds media, changes permissions, sends test pushes and erases devices. Every recipe below uses an explicit device UDID instead of booted. When more than one simulator is running, simctl's own help says it "will choose one of them", so a script can install, push or erase on the wrong device. Pick the device once, keep its UDID in a variable and pass it to every command.
Quick Take
UDID=$(bash sim-boot.sh "iPhone 16e" 27.2) finds or creates the device, boots it and waits until it is ready. Then: install, launch, openurl, addmedia, privacy, push, get_app_container and erase, each with $UDID.
Tested with Xcode 27.2 on iOS 27.2 and 26.5 simulators, using a small SwiftUI app that displays the deep link, push, photo and notification state it receives. The screenshots are from those runs.
Before you start
- Xcode must be selected.
xcrun --find simctlshould print a path inside Xcode. If it says "unable to find utility", see the command-line tools fixes. - Xcode 27 renamed the Simulator app to Device Hub. simctl is unchanged, but
open -a Simulatorfails with "Unable to find application named 'Simulator'". Useopen -a DeviceHub. - Simulators run headless. Every recipe works without a window. Use
xcrun simctl io "$UDID" screenshotto see the result. - Read any subcommand's options with
xcrun simctl help push,help privacyand so on. The help text is the most current reference.
Recipes
- Select a simulator, boot it and wait until it is ready
- Install and launch an app
- Open a deep link
- Add sample photos and videos
- Reset a permission to test the first-run experience
- Send a test push notification
- Find an app's data container
- Reset one simulator
- Dark mode, location, status bar, screenshots and video
1. Select a simulator, boot it and wait until it is ready
Prerequisites: an installed iOS runtime (xcrun simctl list runtimes).
List the available devices, copy the UDID of the one you want, then boot it and block until it has finished booting:
xcrun simctl list devices available
UDID=D4474203-E84D-4CF2-98FB-D4DD15A58852
xcrun simctl bootstatus "$UDID" -b
open -a DeviceHubbootstatus -b boots the device if needed and returns only when it reports Finished, which is what a script needs before installing anything. A plain simctl boot returns earlier. A new device took 24 seconds on our Mac:
$ UDID=$(bash sim-boot.sh "iPhone 16e" 27.2)
Creating "iPhone 16e" on iOS 27.2
Booting iPhone 16e (iOS 27.2) 009FE054-8A32-427D-BD0B-B08399022E8F
Ready.
$ echo $UDID
009FE054-8A32-427D-BD0B-B08399022E8F
$ xcrun simctl bootstatus A6101C14-809C-4B8D-9F47-19000DB16206 -b
Monitoring boot status for Recipe Test iPhone (A6101C14-809C-4B8D-9F47-19000DB16206).
Booting device...
[2026-11-12 00:15:49 +0000] Status=0, isTerminal=NO, Elapsed=00:00.
Booting
[2026-11-12 00:15:51 +0000] Status=1, isTerminal=NO, Elapsed=00:01.
Waiting on BackBoard
[2026-11-12 00:15:52 +0000] Status=2, isTerminal=NO, Elapsed=00:02.
Waiting on Data Migration
...
[2026-11-12 00:16:14 +0000] Status=4294967295, isTerminal=YES, Elapsed=00:24.
FinishedExpected result: the last status is Finished and xcrun simctl list devices booted shows the device. Why not booted? On our test Mac four simulators were running at once, including two named iPhone 17:
$ xcrun simctl list devices booted
== Devices ==
-- iOS 26.5 --
iPhone 17 (1F7CE514-0829-489F-8DD5-9E269120B173) (Booted)
-- iOS 27.2 --
iPhone Duo (170B94C0-CCDF-4073-AD68-B243EC0C87DF) (Booted)
-- iOS 27.2 --
iPhone 17 (ADE54C8A-531C-4E11-9373-47D42D32CFCE) (Booted)
Recipe Test iPhone (A6101C14-809C-4B8D-9F47-19000DB16206) (Booted) The sim-boot.sh script below does the lookup for you: it finds an available device by exact name under the iOS version you give (the newest by default), creates it if it does not exist, waits for the boot and prints the UDID. It shows the device in Simulator on Xcode 26 and in Device Hub on Xcode 27. Copy it and type pbpaste > sim-boot.sh in Terminal to save it.
#!/bin/bash
# sim-boot.sh
# Finds a simulator by exact name (and optional iOS version), creates it if it
# does not exist, boots it, waits until it has finished booting, shows it in
# Simulator (Xcode 26) or Device Hub (Xcode 27) and prints its UDID.
#
# Usage: bash sim-boot.sh "iPhone 18 Pro" # newest installed iOS
# bash sim-boot.sh "iPhone 18 Pro" 26.5 # a specific iOS version
# UDID=$(bash sim-boot.sh "iPhone 18 Pro") # capture the UDID
# Guide: https://appscreens.com/blog/ios-simulator-commands
# Tested: macOS 26.7, Xcode 27.2 (bash 3.2).
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 AppScreens. Free to use, copy and change; please keep
# this header, including the guide link.
set -u
NAME=${1:?Usage: sim-boot.sh "<device type name>" [iOS version]}
VERSION=${2:-}
log() { printf "%s\n" "$*" >&2; }
if [ -z "$VERSION" ]; then
VERSION=$(xcrun simctl list runtimes available | sed -n 's/^iOS \([0-9.]*\) .*/\1/p' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)
fi
[ -n "$VERSION" ] || { log "No iOS runtime is installed. Add one in Xcode > Settings > Components."; exit 1; }
# Look for an existing available device with this exact name under "-- iOS <VERSION> --".
UDID=$(xcrun simctl list devices available | awk -v want="$NAME" -v os="-- iOS $VERSION --" '
function rep(s, n, r) { r = ""; while (n-- > 0) r = r s; return r }
BEGIN { h = "[0-9A-F]"; id = " [(]" rep(h, 8) "-" rep(h, 4) "-" rep(h, 4) "-" rep(h, 4) "-" rep(h, 12) "[)] " }
/^-- / { inos = ($0 == os); next }
inos {
line = $0; sub(/^ +/, "", line)
if (match(line, id) && substr(line, 1, RSTART - 1) == want) { print substr(line, RSTART + 2, 36); exit }
}')
if [ -z "$UDID" ]; then
RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-$(echo "$VERSION" | tr . -)"
log "Creating \"$NAME\" on iOS $VERSION"
UDID=$(xcrun simctl create "$NAME" "$NAME" "$RUNTIME") || exit 1
fi
log "Booting $NAME (iOS $VERSION) $UDID"
xcrun simctl bootstatus "$UDID" -b >/dev/null || exit 1
# Xcode 26 and earlier ship Simulator.app; Xcode 27 replaces it with Device Hub.
SIMAPP="$(xcode-select -p)/Applications/Simulator.app"
if [ -d "$SIMAPP" ]; then
open "$SIMAPP" --args -CurrentDeviceUDID "$UDID"
else
open "devices://device/open?id=$UDID" 2>/dev/null || open -a DeviceHub 2>/dev/null
fi
log "Ready."
echo "$UDID"2. Install and launch an app
Prerequisites: a booted device and an app built for the simulator. In Xcode's DerivedData it is in Build/Products/Debug-iphonesimulator/; a device build will not install. To build to a known folder:
xcodebuild -scheme MyApp -destination "id=$UDID" -derivedDataPath build build
xcrun simctl install "$UDID" build/Build/Products/Debug-iphonesimulator/MyApp.app
xcrun simctl launch "$UDID" com.example.MyAppExpected result: install prints nothing and launch prints the bundle ID and process ID:
$ xcrun simctl install F88646D9-C65A-4399-BC17-89EFC2B986EB RecipeDemo.app
$ xcrun simctl launch F88646D9-C65A-4399-BC17-89EFC2B986EB com.example.RecipeDemo
com.example.RecipeDemo: 97136 Add --console-pty to launch to stream the app's output into Terminal, or --terminate-running-process to restart an app that is already running. Environment variables prefixed with SIMCTL_CHILD_ are passed to the app.
3. Open a deep link
Prerequisites: the app is installed and registers the URL scheme (or associated domain) you are opening.
xcrun simctl openurl "$UDID" "recipedemo://recipe/42?servings=4"Expected result: the app opens and receives the URL. In some of our runs on iOS 27.2 the system first asked "Open in “Recipe Demo”?", and the link arrived only after Open was tapped. simctl exits with status 0 either way, so take a screenshot before deciding a link handler is broken. Quote the URL so the shell does not interpret ? and &.


An unregistered scheme fails with error 115:
$ xcrun simctl openurl A6101C14-809C-4B8D-9F47-19000DB16206 unknownscheme://nothing
An error was encountered processing the command (domain=LSApplicationWorkspaceErrorDomain, code=115):
Simulator device failed to open unknownscheme://nothing.
Underlying error (domain=LSApplicationWorkspaceErrorDomain, code=115):
The operation couldn’t be completed. (LSApplicationWorkspaceErrorDomain error 115.) An https URL opens Safari unless the app's associated domains are set up for it. In XCUITest, open URLs from the test with XCUIApplication.open(_:) instead of shelling out to simctl.
4. Add sample photos and videos
Prerequisites: a booted device and image or video files on your Mac.
xcrun simctl addmedia "$UDID" ~/Desktop/sample-1.jpg ~/Desktop/sample-2.png ~/Desktop/clip.mp4Expected result: nothing is printed and the files appear in Photos. Several files can go in one call; a Live Photo's still and video, exported together, are imported as one Live Photo, and .vcf files go to Contacts. Unsupported files fail:
$ xcrun simctl addmedia 009FE054-8A32-427D-BD0B-B08399022E8F photo.png clip.mp4
$ echo $?
0
$ xcrun simctl addmedia 009FE054-8A32-427D-BD0B-B08399022E8F Info.plist
Failed to import 'Info.plist', error [NSPOSIXErrorDomain] 22: The operation couldn’t be completed. ...
An error was encountered processing the command (domain=com.apple.CoreSimulator.LaunchdSimError, code=133):
Multiple errors were returned; see stderr Need a test video? Record one from the simulator itself. recordVideo keeps running until it receives Control-C (SIGINT), so in a script start it in the background with job control on, otherwise the stop signal is ignored:
set -m
xcrun simctl io "$UDID" recordVideo --codec=h264 --force clip.mp4 &
REC=$!
sleep 5
kill -INT $REC
wait $REC
set +m5. Reset a permission to test the first-run experience
Prerequisites: the app's bundle ID and the matching usage description in its Info.plist.
xcrun simctl privacy "$UDID" reset photos com.example.RecipeDemo
xcrun simctl privacy "$UDID" grant photos com.example.RecipeDemo
xcrun simctl privacy "$UDID" reset all com.example.RecipeDemoExpected result: after reset the app sees .notDetermined and shows its permission prompt again; after grant it sees .authorized without a prompt. Changing a permission can terminate the app if it is running, so relaunch it afterwards. The services you can reset:
$ xcrun simctl help privacy
Usage: simctl privacy <device> <action> <service> [<bundle identifier>]
action
The action to take:
grant - Grant access without prompting. Requires bundle identifier.
revoke - Revoke access, denying all use of the service. Requires bundle identifier.
reset - Reset access, prompting on next use. Bundle identifier optional.
Some permission changes will terminate the application if running.
service
The service:
all - Apply the action to all services.
calendar - Allow access to calendar.
contacts-limited - Allow access to basic contact info.
contacts - Allow access to full contact details.
location - Allow access to location services when app is in use.
location-always - Allow access to location services at all times.
photos-add - Allow adding photos to the photo library.
photos - Allow full access to the photo library.
media-library - Allow access to the media library.
microphone - Allow access to audio input.
motion - Allow access to motion and fitness data.
reminders - Allow access to reminders.
siri - Allow use of the app with Siri.
... Notifications are not on that list. Uninstalling and reinstalling resets the app's permissions: in our test a photos grant did not survive an uninstall, and the reinstalled app reported notDetermined. If the notification prompt still does not return, erase the device (recipe 8). The sim-fresh-install.sh script below does that in one step. Save it the same way, with pbpaste > sim-fresh-install.sh:
#!/bin/bash
# sim-fresh-install.sh
# Reinstalls an app on one simulator as if a user were installing it for the
# first time: terminates and uninstalls the old copy, resets the app's privacy
# permissions, installs the new build and launches it.
#
# Usage: bash sim-fresh-install.sh <UDID> path/to/MyApp.app
# Guide: https://appscreens.com/blog/ios-simulator-commands
# Tested: macOS 26.7, Xcode 27.2 (bash 3.2).
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 AppScreens. Free to use, copy and change; please keep
# this header, including the guide link.
set -u
UDID=${1:?Usage: sim-fresh-install.sh <UDID> path/to/MyApp.app}
APP=${2:?Usage: sim-fresh-install.sh <UDID> path/to/MyApp.app}
BUNDLE=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Info.plist") || exit 1
xcrun simctl bootstatus "$UDID" -b >/dev/null || exit 1
xcrun simctl terminate "$UDID" "$BUNDLE" 2>/dev/null
xcrun simctl uninstall "$UDID" "$BUNDLE" 2>/dev/null
xcrun simctl privacy "$UDID" reset all "$BUNDLE"
xcrun simctl install "$UDID" "$APP" || exit 1
xcrun simctl launch "$UDID" "$BUNDLE"6. Send a test push notification
Prerequisites: the app is installed and has asked for notification permission. Save a payload as push.apns. The Simulator Target Bundle key lets you leave the bundle ID off the command:
{
"Simulator Target Bundle": "com.example.RecipeDemo",
"aps": {
"alert": {
"title": "Dinner is ready",
"body": "Your lasagne timer finished."
},
"sound": "default"
}
}xcrun simctl push "$UDID" push.apns
xcrun simctl push "$UDID" com.example.RecipeDemo push.apnsExpected result: simctl confirms delivery, and the app shows the notification or receives it in the foreground:
$ xcrun simctl push A6101C14-809C-4B8D-9F47-19000DB16206 push.apns
Notification sent to 'com.example.RecipeDemo'
$ xcrun simctl push 009FE054-8A32-427D-BD0B-B08399022E8F com.example.RecipeDemo push-no-target.apns
Notification sent to 'com.example.RecipeDemo'
If you see "Source is not authorized" (UNErrorDomain 2003), the app has no notification authorization yet. It happened on a freshly installed app whose permission request was still pending. The app, open in the foreground, still received the payload, but simctl reported the error; the same command succeeded once the app was authorized.
The payload must have an aps key and be 4096 bytes or less. Only app remote notifications are supported, not VoIP, complication or File Provider pushes. In Xcode 26 and earlier you can also drag an .apns file onto the Simulator window.
$ xcrun simctl push 5509DFEE-93F1-4DEB-B41E-96EF1F36DB93 com.example.RecipeDemo push-no-target.apns
An error was encountered processing the command (domain=UNErrorDomain, code=2003):
Simulator device failed to complete the requested operation.
Underlying error (domain=UNErrorDomain, code=2003):
Repository could not save notification. Source is not authorized.7. Find an app's data container
Prerequisites: a booted device with the app installed. The container lookup fails on a shut-down device.
xcrun simctl get_app_container "$UDID" com.example.RecipeDemo data
open "$(xcrun simctl get_app_container "$UDID" com.example.RecipeDemo data)"Expected result: a path to the app's sandbox, which Finder opens. Use app for the installed bundle, groups for App Group containers, or a group identifier. The user name in this output is replaced:
$ xcrun simctl get_app_container F88646D9-C65A-4399-BC17-89EFC2B986EB com.example.RecipeDemo data
/Users/you/Library/Developer/CoreSimulator/Devices/F88646D9-C65A-4399-BC17-89EFC2B986EB/data/Containers/Data/Application/F7A2717C-402D-463E-8D36-118E50A9EF91
$ ls "$(xcrun simctl get_app_container F88646D9-C65A-4399-BC17-89EFC2B986EB com.example.RecipeDemo data)"
Documents Library SystemData tmp
$ xcrun simctl get_app_container F88646D9-C65A-4399-BC17-89EFC2B986EB com.example.DoesNotExist data
An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=2):
The operation couldn’t be completed. No such file or directory8. Reset one simulator
Prerequisites: the device's UDID. erase returns it to factory state but keeps the device and its UDID; it refuses to run while the device is booted:
xcrun simctl shutdown "$UDID"
xcrun simctl erase "$UDID"$ xcrun simctl erase F88646D9-C65A-4399-BC17-89EFC2B986EB
An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):
Unable to erase contents and settings in current state: Booted
$ xcrun simctl shutdown F88646D9-C65A-4399-BC17-89EFC2B986EB
$ xcrun simctl erase F88646D9-C65A-4399-BC17-89EFC2B986EB
$ xcrun simctl list devices F88646D9-C65A-4399-BC17-89EFC2B986EB
...
Recipe Test iPhone (F88646D9-C65A-4399-BC17-89EFC2B986EB) (Shutdown)Expected result: the device is listed as Shutdown and boots like new. Avoid xcrun simctl erase all unless you mean every simulator. To remove a device completely, use xcrun simctl delete "$UDID"; the Xcode cleanup reference explains devices, runtimes and the space each one uses.
9. Dark mode, location, status bar, screenshots and video
xcrun simctl ui "$UDID" appearance dark
xcrun simctl ui "$UDID" content_size extra-extra-large
xcrun simctl location "$UDID" set 37.3349,-122.0090
xcrun simctl status_bar "$UDID" override --time 9:41 --dataNetwork wifi --wifiBars 3 --cellularBars 4 --batteryState charged --batteryLevel 100
xcrun simctl io "$UDID" screenshot --type=png home.png$ xcrun simctl ui 009FE054-8A32-427D-BD0B-B08399022E8F appearance
light
$ xcrun simctl ui 009FE054-8A32-427D-BD0B-B08399022E8F appearance dark
$ xcrun simctl ui 009FE054-8A32-427D-BD0B-B08399022E8F appearance
dark
$ xcrun simctl location 009FE054-8A32-427D-BD0B-B08399022E8F set 37.3349,-122.0090
$ xcrun simctl location 009FE054-8A32-427D-BD0B-B08399022E8F clear
io screenshot.location start moves the device along waypoints at a set speed, which is useful for maps and fitness apps; location clear stops it.
status_bar changes only the simulator's display. Clear it with status_bar "$UDID" clear. Xcode 27 also adds xcrun simctl reboot "$UDID".
Capturing App Store screenshots
Recipes 1, 7 and 9 make repeatable store captures: one device per screenshot size, sample data in the container, a 9:41 status bar, light and dark appearance, then io screenshot. The 9:41 status bar guide and capture guide cover the details, and AppScreens turns the captures into captioned App Store and Google Play screenshot sets at every required size.
FAQ
Should I use booted or a UDID with simctl?
Use a UDID in scripts. simctl help says that when several devices are booted, booted makes simctl choose one of them, so commands can reach the wrong simulator. Store the UDID in a variable and pass it to every command.
How do I wait until a simulator has finished booting?
Run xcrun simctl bootstatus UDID -b. It boots the device if needed and returns only when the status reaches Finished, which took 24 seconds for a new device in our test.
Why does simctl openurl not open my app?
Check that the app registers the URL scheme, quote the URL, and take a screenshot: on iOS 27.2 an Open in app confirmation sometimes waits for a tap while simctl still exits with status 0. An unregistered scheme fails with LSApplicationWorkspaceErrorDomain error 115.
How do I send a push notification to the iOS Simulator?
Create a JSON payload with an aps key, save it as a .apns file, and run xcrun simctl push UDID com.example.MyApp payload.apns. Add a Simulator Target Bundle key to the payload to omit the bundle ID. If the app has not been granted notification permission yet, simctl can report Source is not authorized even though a foreground app still receives the payload.
How do I reset a permission prompt in the simulator?
Run xcrun simctl privacy UDID reset photos com.example.MyApp, or reset all. The app sees the permission as not determined and asks again. Notifications are not a simctl privacy service. Uninstalling and reinstalling the app resets its permissions, and erasing the device resets everything.
Where is the Simulator app in Xcode 27?
Xcode 27 replaces the Simulator app with Device Hub. Open it with open -a DeviceHub or from Xcode > Open Developer Tool > Device Hub. simctl commands work as before.
Quote this guide
You can quote sections, tables, commands and charts from this guide in your own posts, documentation or answers, as long as you link to this page. Please do not republish the whole guide. The scripts are MIT licensed, so you can copy and change them freely.
AppScreens.com, "iOS Simulator Commands: Practical Recipes for Development and Testing", 27 October 2026. https://appscreens.com/blog/ios-simulator-commandsSources
xcrun simctl helpand the help for each subcommand in Xcode 27.2- Apple: Become a Simulator expert (WWDC20)
- Apple: Adding additional simulators
- Apple: Xcode 27 release notes
- Expo: Xcode 27's Device Hub
Tested on macOS 26.7.2 with Xcode 27.2 (27B52), on iOS 27.2 and 26.5 simulators.





