---
title: "xcrun simctl commands: a reference with examples | Mobster"
description: "Every xcrun simctl command in Xcode 26.4 with its usage line and an example, from devices and apps to push, permissions, location, the status bar and video."
canonical: "https://mobster.dev/blog/simctl-commands"
last-updated: "2026-11-14"
---

Every xcrun simctl command in Xcode 26.4 with its usage line and an example, from devices and apps to push, permissions, location, the status bar and video.

# xcrun simctl commands, with examples

22 October 2026

`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/Developer/usr/bin/simctl`). 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 available
```

What 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](#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" -b
```

Booting 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>] | all
```

`erase` 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/Products/Debug-iphonesimulator/` 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_DE
```

Arguments 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](https://docs.mobster.dev/simulators#deep-links)), 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.YourApp
```

Two 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 clear
```

Write 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.mp4
```

A 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-large
```

The 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 booted
```

Media 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/bin/log help stream` 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 iOS
```

## Read 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](https://mobster.dev/blog/ios-ui-checks-ci-no-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](https://docs.mobster.dev/testing#device-conditions)):

```
conditions:  appearance: dark  permissions: { photos: grant, location: revoke }  push: payloads/order-shipped.json
```

Each key runs one simctl command:

- `appearance`: `ui appearance`
- `content_size`: `ui content_size`
- `location`: `location set`
- `permissions`: `privacy`
- `media`: `addmedia`
- `status_bar`: `status_bar override`
- `push`: `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](https://docs.mobster.dev/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.

## Sources

- [Apple: Xcode 11 Release Notes (status bar overrides)](https://developer.apple.com/documentation/xcode-release-notes/xcode-11-release-notes)
- [Apple: Xcode 11.4 Release Notes (push, privacy, keychain, appearance)](https://developer.apple.com/documentation/xcode-release-notes/xcode-11_4-release-notes)
- [Apple: Generating a remote notification](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification)
- [Apple: Downloading and installing additional Xcode components](https://developer.apple.com/documentation/xcode/downloading-and-installing-additional-xcode-components)
- [Mobster docs: device conditions](https://docs.mobster.dev/testing#device-conditions)
- [Mobster docs: simulators](https://docs.mobster.dev/simulators)

## Try it on your own iPhone

Download Mobster for Mac

## More from the blog

### [How to fix flaky XCUITests](https://mobster.dev/blog/fix-flaky-xcuitests)

19 Oct 2026

### [Run iOS UI checks in CI without a device farm](https://mobster.dev/blog/ios-ui-checks-ci-no-device-farm)

26 Oct 2026

### [Test an iOS app on a real iPhone from your Mac](https://mobster.dev/blog/test-ios-app-on-real-iphone)

17 Oct 2026
