Record the iOS Simulator with simctl
xcrun simctl io recordVideo is the fastest way to capture the Simulator — plus the flags that matter and when a real recorder beats it.

Xcode ships with a command that records the iOS Simulator directly, at native resolution, with no screen capture involved. It is one line, it is free, and most iOS developers do not know it exists.
It is also the wrong tool for about half the jobs people use it for. This covers the command properly, then the three cases where something else is a better answer.
Quick comparison
| Method | Output | Best for | Difficulty |
|---|---|---|---|
simctl io recordVideo | Clean device-native video | CI, bug repros, App Store assets | Easy |
| Simulator's Record Screen | Same, via a menu | One-off, no terminal | Easy |
| Screen recording the Simulator window | Includes window chrome | Showing Xcode alongside | Easy |
| Screenify Studio | Recording plus zoom, captions, frames | Demos and marketing | Easy |
Method 1: xcrun simctl io recordVideo
The command, in full:
xcrun simctl io booted recordVideo demo.movbooted means the currently running Simulator. Press Ctrl+C to stop — the file finalises on interrupt, so do not force-quit the terminal.
That is the whole basic usage. The flags are where it gets useful.
The flags worth knowing
xcrun simctl io booted recordVideo \
--codec=h264 \
--mask=black \
--force \
demo.mp4--codec takes h264 or hevc. The default is HEVC, which is smaller but occasionally awkward outside Apple platforms. Use h264 if the file is going to a colleague on Windows or into a tool that chokes on HEVC.
--mask controls what happens to the rounded corners and the notch or Dynamic Island area. black fills them with black, ignored gives you the raw rectangle, alpha produces transparency — genuinely useful if you are compositing the recording into a device frame later.
--force overwrites an existing file rather than erroring. Essential in scripts.
--display picks internal or external on Simulators that support an external display.
Targeting a specific Simulator
booted is ambiguous when several Simulators are running. List them and use a UDID:
# every available device, with UDIDs
xcrun simctl list devices available
# record a specific one
xcrun simctl io 5A3F8C21-... recordVideo demo.mp4Stopping cleanly from a script
Ctrl+C is fine interactively. In a script, send SIGINT rather than SIGKILL, or the file will be unplayable:
xcrun simctl io booted recordVideo --force demo.mp4 &
REC_PID=$!
# ... run your UI test here ...
kill -INT $REC_PID
wait $REC_PIDkill -9 skips the finalisation step and leaves you with an unreadable file. This is the single most common way people break this in CI.
Good for: CI pipelines, bug reproductions attached to tickets, and clean source footage for App Store previews. The output is device-native — no window chrome, no desktop, no scaling.
Bad for: anything that needs the Simulator shown in context alongside Xcode or a browser. It records the device screen only, by design.
Method 2: the Simulator's own menu
No terminal needed.
Step 1 — with the Simulator focused, choose File → Record Screen, or press ⌘R.
Step 2 — perform your interaction.
Step 3 — click the stop button in the toolbar, or ⌘R again.
Step 4 — the recording appears in a preview window; drag it out or save it.
Same underlying capture as simctl, without the flags.
Good for: a quick one-off when you do not want to think about codecs.
Bad for: scripting, and any case where you need --mask=alpha or a specific codec.
Method 3: screen recording the Simulator window
Sometimes the device screen alone is not the story.
If you are demonstrating a debugging workflow, showing a breakpoint hit while the app pauses, or comparing the Simulator against a design in Figma, you need the desktop context. Then an ordinary screen recorder capturing the Simulator window is correct and simctl is not.
Record the window rather than the display, so the rest of your desktop stays out.
Good for: tutorials about development, not about the app.
Bad for: App Store assets, which must be device-native at exact pixel dimensions.
Method 4: recording for humans rather than machines
simctl gives you a technically perfect capture. It does nothing to make that capture watchable.
For a demo going on a landing page or a launch post, the missing pieces are the same ones missing from any raw capture: nobody can read the small UI at embed size, there is dead air at both ends, and it does not read as a product shot.
Screenify Studio records the Simulator directly as a capture source — as well as a real iPhone over USB — and adds the finishing in the same project: auto-zoom driven by the interactions it captured, on-device captions, and device frames that put the recording inside an iPhone body with a background. There are 47 flat iOS device models plus 3D stages.
It is not a replacement for simctl in CI. It is what you use when the audience is a person rather than a test report.
Try Screenify Studio — free, unlimited recordings
Auto-zoom, AI captions, dynamic backgrounds, and Metal-accelerated export.
Wiring it into a UI test run
The reason to prefer the command over the menu item is that it composes. A recording attached to every failing test turns "cannot reproduce" into a video.
A wrapper that records a test run and keeps the file only on failure:
#!/bin/bash
set -o pipefail
OUT="artifacts/$(date +%s).mp4"
mkdir -p artifacts
xcrun simctl io booted recordVideo --codec=h264 --force "$OUT" &
REC=$!
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15' \
| xcpretty
STATUS=$?
kill -INT $REC
wait $REC 2>/dev/null
if [ $STATUS -eq 0 ]; then
rm -f "$OUT" # passing runs do not need a video
echo "tests passed"
else
echo "tests failed — recording at $OUT"
fi
exit $STATUSThree details in there matter more than they look:
set -o pipefail. Without it, pipingxcodebuildthroughxcprettygives you xcpretty's exit code, and every run looks like it passed.kill -INTthenwait. As above — the recording finalises on SIGINT, andwaitgives it time to finish writing before the script exits and the shell tears the process down.- Deleting on success. A recording per test run fills a disk quickly. Keep failures, discard the rest.
For per-test rather than per-run recordings, start and stop around each case in your test harness's setup and teardown, naming files after the test. Noisier, but far more useful when one case in forty is flaky.
Booting a Simulator that is not already running
booted assumes something is running. In a fresh CI environment, nothing is:
# create a device if it does not exist, then boot and wait
DEVICE=$(xcrun simctl create ci-iphone \
com.apple.CoreSimulator.SimDeviceType.iPhone-15 \
com.apple.CoreSimulator.SimRuntime.iOS-17-0)
xcrun simctl boot "$DEVICE"
xcrun simctl bootstatus "$DEVICE" -b # blocks until fully booted
xcrun simctl io "$DEVICE" recordVideo --force out.mp4 &bootstatus -b is the part people miss. Recording against a Simulator that is booting but not ready produces the first several seconds as a black or partially-drawn screen.
Clean up afterwards so devices do not accumulate:
xcrun simctl shutdown "$DEVICE"
xcrun simctl delete "$DEVICE"App Store previews specifically
If the recording is destined for App Store Connect, the requirements are strict and simctl is well suited to meeting them.
Record on a Simulator matching a required device size. App Store Connect asks for specific resolutions. Recording on the right Simulator gets exact pixel dimensions with no scaling.
Keep it 15 to 30 seconds. That is the hard limit, and 30 seconds is far shorter than it sounds.
Use --codec=h264 unless you have a reason not to — broadest compatibility through the upload pipeline.
Do not add a device frame. App Store previews display inside Apple's own device chrome. A frame in your video means a frame inside a frame.
Record silent, add audio later. Simulator audio routing is unreliable, and previews autoplay muted anyway. Text overlays carry more than narration here.
The companion commands worth knowing
recordVideo is one verb in a larger toolbox, and two of its neighbours matter for anyone producing App Store assets.
A clean status bar
Apple's own screenshots always show full signal, full battery and a tidy time. Yours show 43% battery and whatever the clock says, which reads as amateur next to them. simctl status_bar overrides it:
xcrun simctl status_bar booted override \
--time "09:41" \
--dataNetwork wifi \
--wifiMode active \
--wifiBars 3 \
--cellularMode active \
--cellularBars 4 \
--batteryState charged \
--batteryLevel 10009:41 is the time Apple uses in its own marketing, which is why every Apple device in an advert shows it. Clear the override afterwards:
xcrun simctl status_bar booted clearSet this before you start recording. The override applies to the live Simulator, so a recording started first will capture the real status bar until the moment the command runs.
Still screenshots
Same shape as recording, for App Store screenshot slots:
xcrun simctl io booted screenshot --type=png shot.png--mask works here too, so --mask=alpha gives you a screenshot with transparent corners ready to composite into a device frame.
Deep links and app state
Useful for scripting a recording that always starts from the same place:
# open a deep link
xcrun simctl openurl booted "myapp://product/42"
# reset the app's data so the demo starts clean every time
xcrun simctl privacy booted reset all com.example.MyApp
xcrun simctl uninstall booted com.example.MyApp
xcrun simctl install booted path/to/MyApp.app
xcrun simctl launch booted com.example.MyAppThe reason to script the setup rather than click through it: a demo recording you have to re-shoot for a small change is a demo you re-shoot badly the third time. Scripted setup means take four is identical to take one.
Appearance and locale
Two more that change what the recording looks like without touching your app:
# dark mode
xcrun simctl ui booted appearance dark
# increase content size for an accessibility pass
xcrun simctl ui booted content_size extra-extra-largeRecording the same flow in light and dark, or at a large content size, takes seconds when it is a command and half an hour when it is a settings walk.
Putting the whole asset pipeline together
For App Store previews specifically, the sequence that produces consistent output every time:
- Boot the exact Simulator model the store requires, and wait with
bootstatus -b. - Install a fresh build and reset app data, so no leftover state appears in frame.
- Override the status bar to 09:41, full bars, full battery.
- Set appearance to match the screenshots you are pairing it with.
- Deep-link to the starting screen rather than navigating to it.
- Start
recordVideowith--codec=h264 --force. - Run the interaction — ideally as a UI test, so it is identical every time.
kill -INT,wait.- Clear the status bar override.
- Trim to under 30 seconds.
Steps 1 to 5 are the ones people skip, and they are the reason a second recording never quite matches the first.
Simulator versus a real device
simctl only records the Simulator, and there are cases where that is the wrong source no matter how convenient the command is.
Record the Simulator when the app is UI-driven, the flow is deterministic, you need exact device pixel dimensions, or the capture is going into CI. It is faster to script, gives cleaner output, and never has a fingerprint smudge on the lens.
Record a real device when any of the following are involved:
- Camera, LiDAR, or ARKit. The Simulator fakes or omits these entirely.
- Performance. The Simulator runs your app on a desktop CPU with a desktop GPU. Animations that stutter on a three-year-old iPhone look flawless in the Simulator, which makes it actively misleading as a demo of responsiveness.
- Haptics, Face ID, or the physical shape of interaction. A demo where the value is how something feels cannot be made in a Simulator.
- Push notifications from a real service, though
simctl pushcan simulate a payload if you only need the visual. - Anything metal-heavy or graphically intensive, where the rendering path genuinely differs.
The honest failure mode here is showing a Simulator recording as evidence that the app is fast. It is not evidence of that, and reviewers who know the difference will notice the tell — Simulator recordings have a particular smoothness that real devices at 60 Hz do not quite match.
For a real device, connect over USB and either use QuickTime's Movie Recording with the iPhone as the camera source, or a recorder that lists connected iOS devices as a capture source. Both give you the real thing at real speed.
Trimming and finishing the output
recordVideo gives you the raw take. Two steps make it usable.
Trim the ends. Every recording starts with a moment of you moving to the Simulator and ends with a reach for Ctrl+C. ffmpeg does this without re-encoding:
# keep from 1.5s to 18s, no quality loss
ffmpeg -ss 1.5 -to 18 -i demo.mp4 -c copy trimmed.mp4Stream-copy trimming lands on the nearest keyframe, so the cut may be off by a fraction. For frame-accurate cuts, drop -c copy and accept a re-encode.
Check the dimensions before uploading anywhere with requirements:
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,duration -of csv=p=0 trimmed.mp4App Store Connect rejects on both dimensions and duration, and finding out at upload time is a slow way to learn.
Troubleshooting
"Invalid device: booted". No Simulator is running, or several are and none is unambiguously booted. Run xcrun simctl list devices booted and pass an explicit UDID.
The file will not play. Almost always a kill -9 instead of kill -INT. The recording finalises on SIGINT; killed hard, the index is never written. Use -INT and wait.
The recording is enormous. HEVC at Simulator resolution is still large for a long capture. Switch to --codec=h264 and re-encode afterwards, or record shorter takes. See why screen recordings are so large.
No audio in the recording. recordVideo captures video only. Simulator audio goes to your Mac's output; to capture it you need a system-audio route or a full recorder.
Rounded corners look wrong. That is --mask. Use black for a normal video, alpha if you plan to composite over a background.
The recording is choppy. The Simulator is competing with your app's build, an Instruments session, or an indexing Xcode. Close what you can. Simulator rendering is not free.
xcrun: error: unable to find utility "simctl". Command line tools are not pointed at a full Xcode install:
sudo xcode-select -s /Applications/Xcode.app/Contents/DeveloperRecording works locally but not in CI. The runner needs a booted Simulator and a real user session. Headless agents without a window server cannot record.
FAQ
What is the command to record the iOS Simulator?
xcrun simctl io booted recordVideo demo.mov, stopped with Ctrl+C. Add --codec=h264 for wider compatibility and --force to overwrite.
How do I stop simctl recordVideo in a script?
Send SIGINT — kill -INT $PID then wait $PID. A hard kill leaves an unplayable file because the index is written during finalisation.
Can simctl record audio?
No. recordVideo is video only. Audio needs a system-audio capture route or a different recorder.
What does --mask do?
Controls the rounded corners and notch region. black fills them, ignored leaves the raw rectangle, alpha makes them transparent for compositing.
Can I record a real iPhone this way?
No — simctl is Simulator-only. For a physical device, connect over USB and use QuickTime's Movie Recording, or a recorder that supports iOS devices as a source.
Is the Simulator recording the same quality as a real device? Resolution matches, but rendering differs. Animation timing, Metal behaviour and camera or sensor features are not identical. For App Store previews of graphically simple apps the Simulator is fine; for anything performance-sensitive, record the real device.
Why is my recording rejected by App Store Connect? Usually duration over 30 seconds, or resolution not matching a supported device size. Recording on the exact required Simulator avoids the second.
Does this work with visionOS or watchOS Simulators?
simctl io recordVideo works across Simulator platforms, though mask options and available displays differ.
How do I get a clean status bar like Apple's screenshots?
xcrun simctl status_bar booted override --time "09:41" --batteryLevel 100 --wifiBars 3, run before you start recording. Clear it afterwards with status_bar booted clear.
Can I record two Simulators at once?
Yes — run a recordVideo per device UDID in parallel, each writing to its own file. Both compete for the same encoder, so expect dropped frames if either is graphically heavy.
Where does the file go if I do not give a path? The command requires an output path; there is no default location. Pass an absolute path in scripts, because the working directory in CI is rarely what you assume.
Is the recording frame rate fixed? There is no frame-rate flag. Output tracks the Simulator's rendering, so a Simulator dropping frames produces a recording that drops them too. Close other work before a take that matters.
Should I add a device frame to a Simulator recording? Not for App Store previews — Apple displays those inside its own device chrome, so a frame in your video means two frames. For a landing page or a launch post, a frame helps, and that is a finishing step rather than a capture one.
The short version
xcrun simctl io booted recordVideo --codec=h264 --force demo.mp4, Ctrl+C to stop, and in scripts always kill -INT rather than kill -9.
Use it for CI, bug repros and App Store source footage. When the audience is a person deciding whether to download the app, the capture is the raw material rather than the deliverable.
Related reading: Screen recorders that zoom in · Why screen recordings are so large · Fix macOS screen recording permission
Try Screenify Studio
Record your screen with auto-zoom, AI captions, dynamic backgrounds, and Metal-accelerated export. Free plan, unlimited recordings.
Download Free