xcrun simctl commands, with examples
xcrun simctl is the command line for the iOS Simulator. It creates and boots simulators, installs and launches apps, and sets the state a test needs: a push notification, a permission, a location, a status bar, a text size. This page lists each subcommand by the job it does, with its usage line, an example you can paste and what it prints.
Every usage line here is from xcrun simctl help <subcommand> in Xcode 26.4 (17E192). Run the same command on your Mac to see your version’s flags.
Before you start
Most subcommands take a <device>. It is a simulator’s UDID, or the word booted, which picks a booted simulator. The help is plain about the catch: “If multiple devices are booted when the “booted” device is selected, simctl will choose one of them.” Use booted at your desk and a UDID in scripts.
boot, launch and spawn pass environment variables to the simulator when you prefix them with SIMCTL_CHILD_ in your own shell: SIMCTL_CHILD_API_URL=http://localhost:8080 xcrun simctl launch booted com.example.YourApp starts the app with API_URL set.
xcrun simctl itself is a short shell script in Xcode (Contents/). It checks the installed CoreSimulator framework’s version, runs xcodebuild -runFirstLaunch when it isn’t the one this Xcode expects, then hands your arguments to CoreSimulator’s own simctl. That is why the first command after you switch Xcode can take a minute.
Find and create simulators
list
Usage: simctl list [-j | --json] [--json-fd=<fd>] [--json-output=<file path>] [-e | --no-escape-slashes] [-v] [devices|devicetypes|runtimes|pairs] [<search term>|available]Name a type to list only that, and add a search term to filter it. The term available keeps the items this Xcode can run.
xcrun simctl list runtimesxcrun simctl list devicetypes "iPhone 17"xcrun simctl list devices availableWhat Xcode 26.4 printed for the first two:
== Runtimes ==iOS 26.4 (26.4 - 23E244) - com.apple.CoreSimulator.SimRuntime.iOS-26-4== Device Types ==iPhone 17 Pro (com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro)iPhone 17 Pro Max (com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro-Max)iPhone 17e (com.apple.CoreSimulator.SimDeviceType.iPhone-17e)iPhone 17 (com.apple.CoreSimulator.SimDeviceType.iPhone-17)list devices prints one line per simulator under its runtime: the name, the UDID in brackets and the state, such as iPhone 17 Pro (<UDID>) (Shutdown).
-j prints the same as JSON; Read the JSON with jq has the queries.
create
Usage: simctl create <name> <device type id> [<runtime id>]Leave out the runtime and simctl picks the newest one that runs the device type. It prints the new simulator’s UDID, so a script can keep it:
UDID=$(xcrun simctl create "CI iPhone" com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro com.apple.CoreSimulator.SimRuntime.iOS-26-4)clone, rename, delete
delete takes UDIDs, unavailable or all. delete unavailable removes simulators whose runtime this Xcode no longer has, the usual leftover after an Xcode update; delete all removes every simulator in the set, yours included. rename <device> <name> and clone <device> <new name> do what they say.
For tests that must not see each other’s data, create each simulator fresh instead of cloning one. In our own benchmark runs, apps on a clone resolved to the source simulator’s containers, so our harness refuses a cloned simulator.
Boot, wait, shut down and erase
boot and bootstatus
Usage: simctl boot <device> [--arch=<arch>] [--disabledJob=<job>] [--enabledJob=<job>] [--checked-allocations]Usage: simctl bootstatus <device> [-bcd]boot returns before the simulator is ready for an app. bootstatus isn’t in simctl help’s list, but simctl help bootstatus documents it: it prints boot progress until the device finishes booting, and -b boots the device first if it isn’t booted. In a script, that one line replaces a boot and a sleep:
xcrun simctl bootstatus "$UDID" -bBooting a simulator that is already booted fails with Unable to boot device in current state: Booted. Check state in list -j first, or let bootstatus -b decide.
shutdown and erase
Usage: simctl shutdown <device> | allUsage: simctl erase <device> [... <device n>] | allerase returns a simulator to a new one’s contents and settings: apps, data, permissions and the keychain. The simulator must be shut down first.
xcrun simctl shutdown "$UDID"xcrun simctl erase "$UDID"Install, launch and remove an app
Usage: simctl install <device> <path>Usage: simctl launch [-w | --wait-for-debugger] [-a <arch> | --arch=<arch>] [--console|--console-pty] [--stdout=<path>] [--stderr=<path>] [--terminate-running-process] [--checked-allocations] <device> <app bundle identifier> [<argv 1> <argv 2> ... <argv n>]Usage: simctl terminate <device> <app bundle identifier>Usage: simctl uninstall <device> <app bundle identifier>install takes the .app a simulator build makes, such as the one under Build/ in your derived data. A device build or an .ipa won’t install on a simulator.
xcrun simctl install booted Build/ Products/ Debug-iphonesimulator/ YourApp.appxcrun simctl launch --terminate-running-process booted com.example.YourApp -AppleLanguages "(de)" -AppleLocale de_DEArguments after the bundle ID go to the app as launch arguments, which is how a test sets the app’s language without changing the simulator’s. --console keeps the command attached and prints the app’s stdout and stderr in your terminal; the help notes that log output usually goes to stderr. --terminate-running-process quits a running copy first, so the launch is always a cold start.
listapps, appinfo and get_app_container
Usage: simctl listapps <device>Usage: simctl appinfo <device> <bundle identifier>Usage: simctl get_app_container <device> <app bundle identifier> [<container>]get_app_container prints a path on your Mac. The container is app (the .app bundle, the default), data (Documents, Library and tmp), groups, or one App Group’s identifier. open "$(xcrun simctl get_app_container booted com.example.YourApp data)" opens the data container in Finder. That is the fastest way to read what your app wrote to disk during a test, or to delete one file without erasing the simulator.
Open a URL or a deep link
Usage: simctl openurl <device> <URL>xcrun simctl openurl booted "yourapp://tasks/ 42"xcrun simctl openurl booted "https://example.com/ products/ 42"A custom scheme opens your app when its Info.plist declares the scheme. The first time on each simulator, iOS asks “Open in …?” before it hands the link over (Mobster simulator docs), so a test that opens links this way has to press Open once.
Send a push notification
Usage: simctl push <device> [<bundle identifier>] (<json file> | -)The payload must be a JSON object with an aps key and be 4,096 bytes or less, the same 4,096-byte limit Apple sets for remote notifications other than VoIP. Only app remote notifications work: the help says “VoIP, Complication, File Provider, and other types are not supported.”
{ "Simulator Target Bundle": "com.example.YourApp", "aps": { "alert": { "title": "Order shipped", "body": "Your order is on its way." }, "badge": 1, "sound": "default" }}With Simulator Target Bundle in the file, the bundle ID argument is optional; when you pass both, the argument wins.
xcrun simctl push booted payload.jsonecho '{"aps":{"alert":"Hello"}}' | xcrun simctl push booted com.example.YourApp -On success simctl prints a line that starts Notification sent to. Keep Simulator Target Bundle out of payloads your server sends.
Grant, revoke and reset permissions
Usage: simctl privacy <device> <action> <service> [<bundle identifier>]The actions are grant and revoke, which need a bundle ID, and reset, which prompts again on next use. The services in Xcode 26.4 are all, calendar, contacts-limited, contacts, location, location-always, photos-add, photos, media-library, microphone, motion, reminders and siri.
xcrun simctl privacy booted grant photos com.example.YourAppxcrun simctl privacy booted revoke location com.example.YourAppxcrun simctl privacy booted reset all com.example.YourAppTwo warnings come with it. “Some permission changes will terminate the application if running”, so set permissions before you launch. And a grant skips the prompt, so it also skips the check that your Info.plist has the usage description; the help says that bypass “can mask bugs”. Keep one test that sees the real prompt.
Camera and Face ID aren’t in the list.
Set the location
Usage: simctl location <device> <action> [arguments]The actions are list, clear, set <lat>,<lon>, run <scenario>, and start, which moves along waypoints at a speed you set (20 m/s unless you say otherwise):
xcrun simctl location booted set 52.5200,13.4050xcrun simctl location booted start --speed=1.4 --interval=1 52.5200,13.4050 52.5163,13.3777xcrun simctl location booted clearWrite coordinates with . as the decimal separator and , between latitude and longitude. location <device> list prints the built-in scenarios that run accepts.
Override the status bar for screenshots
Usage: simctl status_bar <device> [list | clear | override <override arguments>]The overrides are --time, --dataNetwork, --wifiMode, --wifiBars (0 to 3), --cellularMode, --cellularBars (0 to 4), --operatorName, --batteryState and --batteryLevel (0 to 100). Apple’s example from the Xcode 11 release notes:
xcrun simctl status_bar booted override --time "9:41" --batteryState charged --batteryLevel 100xcrun simctl status_bar booted clear--time takes an ISO date too, and then sets the date as well as the clock. Clear the overrides at the end of a run, so the next test starts from the real status bar.
Take screenshots and record video
io screenshot [--type=<type>] [--display=<display>] [--mask=<policy>] <file or url>io recordVideo [--codec=<codec>] [--display=<display>] [--mask=<policy>] [--force] <file or url>xcrun simctl io booted screenshot home.pngxcrun simctl io booted recordVideo --codec=h264 run.mp4A screenshot is a PNG unless the extension or --type says tiff, bmp, gif or jpeg; simctl prints Detected file type from extension: and then Wrote screenshot to: with the path. - writes the PNG to stdout.
recordVideo uses HEVC unless you pass --codec=h264, which more players and CI artifact viewers open. It writes Recording started to stderr once the first frame is in, and it stops on SIGINT, finishing the file before it exits. In a script, start it in the background and stop it with kill -INT:
xcrun simctl io "$UDID" recordVideo --codec=h264 --force run.mp4 &REC=$!# ... run the test ...kill -INT "$REC"; wait "$REC"On a display that isn’t a rectangle, --mask=black fills the masked parts, such as the rounded corners, with black; ignored keeps the whole framebuffer.
Appearance, contrast and text size
Usage: simctl ui <device> <option> [<arguments>]appearance, increase_contrast and content_size each print the current value when you give no argument, and set it when you do:
xcrun simctl ui booted appearance darkxcrun simctl ui booted increase_contrast enabledxcrun simctl ui booted content_size accessibility-extra-largeThe text sizes run from extra-small to extra-extra-extra-large, then the accessibility sizes from accessibility-medium to accessibility-extra-extra-extra-large.
Photos, contacts, certificates and the pasteboard
Usage: simctl addmedia <device> <path> [... <path>]Usage: simctl keychain <device> <action> [arguments]addmedia takes photos, videos and contacts (vCard) in one call, and pairs a photo and video into a Live Photo by itself. keychain has add-root-cert, add-cert and reset; a root certificate is how a simulator trusts a local HTTPS proxy. pbcopy reads your stdin into the simulator’s pasteboard and pbpaste prints it back.
xcrun simctl addmedia booted fixtures/ receipt.jpg fixtures/ team.vcfxcrun simctl keychain booted add-root-cert ~/ .mitmproxy/ mitmproxy-ca-cert.pemecho "SAVE20" | xcrun simctl pbcopy bootedMedia you add stays in the library until you erase the simulator.
Logs and diagnostics
spawn runs a program inside the simulator. The iOS 26.4 runtime ships log in its /usr/bin, so you can stream your app’s logs with the options of log stream on your Mac (/usr/ lists them):
Usage: simctl spawn [-w | --wait-for-debugger] [-s | --standalone] [-a <arch> | --arch=<arch>] [--checked-allocations] <device> <path to executable> [<argv 1> <argv 2> ... <argv n>]xcrun simctl spawn booted log stream --level debug --style compact --predicate 'subsystem == "com.example.YourApp"'getenv prints one environment variable from a booted simulator. diagnose collects logs into an archive for a bug report; --data-containers adds app data, and the help warns that it “May include private information”. logverbose <device> enable turns on verbose logging; the help says you may need to reboot the simulator before it takes effect.
Runtimes
Usage: simctl runtime <operation> <arguments>runtime list shows each runtime disk image with its build and state, and the total size. On this Mac it listed one, iOS 26.4 (23E244), Ready, at 9.9 GB.
runtime delete --notUsedSinceDays 30 --dry-run shows what an old-runtime cleanup would remove before it does. runtime add adds a runtime disk image you already have. To download one, use xcodebuild:
xcodebuild -downloadPlatform iOSRead the JSON with jq
list -j prints everything as JSON. Devices are grouped under their runtime’s identifier, and each one has dataPath, dataPathSize, deviceTypeIdentifier, isAvailable, lastBootedAt, logPath, logPathSize, name, state and udid. These queries ran against Xcode 26.4:
# Each runtime's name, build and identifierxcrun simctl list -j runtimes | jq -r '.runtimes[] | "\(.name) \(.buildversion) \(.identifier)"'# iOS 26.4 23E244 com.apple.CoreSimulator.SimRuntime.iOS-26-4# The UDID of a simulator by namexcrun simctl list -j devices available | jq -r '.devices[][] | select(.name == "CI iPhone") | .udid'Use isAvailable, not the old availability string: Apple removed that field from the JSON in Xcode 11.
Run simctl in CI
A CI job that owns its simulator from start to end doesn’t flake on someone else’s state:
xcodebuild -runFirstLaunchUDID=$(xcrun simctl create "ci-$GITHUB_RUN_ID" com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro com.apple.CoreSimulator.SimRuntime.iOS-26-4)xcrun simctl bootstatus "$UDID" -bxcrun simctl status_bar "$UDID" override --time "9:41" --batteryState charged --batteryLevel 100# ... xcodebuild test -destination "id=$UDID" ...xcrun simctl shutdown "$UDID"xcrun simctl delete "$UDID"xcodebuild -runFirstLaunch goes first, so the simctl wrapper’s own check never runs in the middle of a test step. Use the UDID everywhere, and delete the simulator in a step that runs on failure too, or each failed job leaves one behind.
Run iOS UI checks in CI without a device farm has the whole workflow file around these steps.
Where a Mobster check sets these for you
A Mobster check declares the state it needs in a conditions block. Mobster runs the simctl commands above before the check. When it ends, Mobster restores the appearance and text size, clears location and status-bar overrides, and resets changed permissions so they prompt again (Device conditions):
conditions: appearance: dark permissions: { photos: grant, location: revoke } push: payloads/ order-shipped.jsonEach key runs one simctl command:
appearance:ui appearancecontent_size:ui content_sizelocation:location setpermissions:privacymedia:addmediastatus_bar:status_bar overridepush:push, once the app is in front
locale isn’t a simctl command: Mobster passes -AppleLanguages and -AppleLocale to the app at launch, as in the launch example above. A condition that can’t be set makes the check couldnt_run, so it never runs in the wrong state. On a real iPhone only appearance and locale apply; the rest change a phone’s state, so Mobster skips them and says so in the report.
Mobster boots its own simulators headless with simctl, never opens Simulator.app, and leaves the simulators you made alone (Simulators). simctl has no Face ID or Touch ID subcommand; mobster sim biometric enrolls a simulator and sends a match or a non-match to your app’s prompt.
Questions
How do I find a simulator's UDID?
Run xcrun simctl list devices available and read the UUID in brackets after the name, or select it by name from xcrun simctl list -j devices available with jq.
Can simctl send a push notification or change permissions on a real iPhone?
No. simctl drives simulators only. simctl push sends app remote notifications to a simulator; VoIP, Complication and File Provider pushes aren't supported.



