
Fastlane Screenshots: snapshot, frameit, and deliver on Current iPhones and iPads
How to capture App Store screenshots with fastlane snapshot, frame them with frameit, and upload them with deliver, with the simulator names, sizes, and frame gaps that apply to fastlane 2.241.2.
On this page
Fastlane makes App Store screenshots with three actions: snapshot runs your UI tests on every simulator and language you list and saves a PNG wherever a test calls snapshot("name"), frameit puts those PNGs inside device frames, and deliver uploads the folder to App Store Connect. On fastlane 2.241.2, released October 26, 2026, deliver accepts every current iPhone and iPad size, while frameit still has no frame for the iPhone Air, the 13-inch iPad, or iPhone Duo.
Guides written before Apple's current iPhone sizes list an iPhone 8 Plus or an iPhone X in their Snapfiles, and the setup around those device names has barely moved since. You add a helper file to a UI test target, write one test that walks through the screens you want, list devices and languages in a Snapfile, and run one command that produces a folder per language. What has changed is the device list, and the gaps sit in different places for each action. We read the snapshot, frameit, and deliver source on October 28, 2026 to check which sizes each one accepts, and everything below about fastlane's behavior comes from that code or its open issues.
What each action does with your files
| Action | What it does | Reads | Writes |
|---|---|---|---|
snapshot (capture_screenshots) |
Builds the app, runs a UI test per device and language, saves PNGs | Snapfile, a UI test target | fastlane/screenshots/<locale>/ |
frameit (frame_screenshots) |
Adds a device frame, and optionally a background and title | Captures, Framefile.json, title.strings |
<name>_framed.png beside each capture |
deliver (upload_to_app_store) |
Uploads screenshots (and metadata, if you let it) to App Store Connect | fastlane/screenshots/<locale>/ |
The editable app version's screenshot sets |
Android has its own pair: screengrab captures from instrumentation tests and supply uploads to Google Play. That route gets a short section near the end.
Setting up snapshot in a UI test target
From the folder that holds your .xcodeproj, run fastlane snapshot init. It writes a Snapfile and a SnapshotHelper.swift into ./fastlane. Drag SnapshotHelper.swift into your UI test target (create one in Xcode under File, then New, then Target if the project doesn't have it yet), and make sure the UI test target is in a shared scheme, because snapshot runs it through xcodebuild.
The test is ordinary XCUITest code with two extra calls:
import XCTest
final class ScreenshotTests: XCTestCase {
@MainActor
func testStoreScreenshots() {
let app = XCUIApplication()
app.launchArguments += ["-demoData", "YES"]
setupSnapshot(app)
app.launch()
snapshot("01-home")
app.buttons["Plans"].tap()
snapshot("02-plans")
app.buttons["Settings"].tap()
snapshot("03-settings")
}
}
setupSnapshot(app) has to come before app.launch(), since it adds the language and locale arguments for the current run. The -demoData flag is your own: read it in the app and load a believable sample account, so every run shows the same names and numbers. Each snapshot() call waits up to 20 seconds for the network activity indicator to clear before it saves, which you can change with snapshot("01-home", timeWaitingForIdle: 5). The helper captures XCUIScreen.main, so the file is the screen alone, with no Simulator bezel around it.
Then fill in the Snapfile:
devices([
"iPhone 18 Pro Max",
"iPad Pro 13-inch (M5)"
])
languages([
"en-US",
"de-DE",
"ja"
])
scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)
override_status_bar(true)
override_status_bar(true) sets 9:41, full battery, and full signal on every simulator before the test starts. Device names have to match xcrun simctl list devices exactly, including the chip suffix on iPads, and snapshot uses the newest iOS runtime unless you set ios_version("26.5") to pick an older one. Run fastlane snapshot and you get one folder per language, each holding files like iPhone 18 Pro Max-01-home.png, plus an HTML summary that opens when the run finishes.
Simulator names for each App Store slot
The table pairs current simulators with the size each one captures, the App Store Connect set deliver puts it in, and the frame frameit picks. Capture sizes come from our Simulator screenshot guide, and the last two columns come from deliver's DEVICE_RESOLUTIONS table and frameit's device_types.rb.
| Snapfile name | Capture size | deliver set | frameit frame |
|---|---|---|---|
| iPhone 18 Pro Max | 1320 x 2868 | 6.9-inch (APP_IPHONE_67) |
iPhone 17 Pro Max (no iPhone 18 yet) |
| iPhone 17 Pro Max | 1320 x 2868 | 6.9-inch (APP_IPHONE_67) |
iPhone 17 Pro Max |
| iPhone Air | 1260 x 2736 | 6.9-inch (APP_IPHONE_67) |
None, "Unsupported screen size" |
| iPad Pro 13-inch (M5) | 2064 x 2752 | 13-inch (APP_IPAD_PRO_3GEN_129) |
None, "Unsupported screen size" |
| iPhone Duo | 2007 x 2853 inner, 1398 x 2034 outer | iPhone Duo (APP_IPHONE_DUO) |
None yet |
For most iPhone apps, one Pro Max simulator is enough: App Store Connect scales its 6.9-inch captures into the required 6.3-inch slot and down for smaller iPhones. frameit picks a frame by pixel size, so an iPhone 18 Pro Max capture gets the iPhone 17 Pro Max bezel, the highest-ranked frame at 1320 x 2868. The 6.9-inch size page lists the landscape sizes for the same class.
iPhone Duo is the awkward one. Its frame images were added to the frameit-frames repository in February, but frameit's device list has no entry for them, and a pull request adding Duo support was still open on October 28. Capture is also unclear, because the helper saves XCUIScreen.main and the Duo simulator has two displays. Our Simulator guide shows how to save each Duo screen with simctl if snapshot gives you only one of them. Apple requires Duo screenshots for submissions from April 2027, so plan on that set coming from a separate step for now.
Framing with frameit, and when to skip it
frameit needs ImageMagick (brew install imagemagick) and downloads its frames on first run. Running fastlane frameit inside the screenshots folder with no config adds a bezel to each capture and saves a _framed.png copy. That output is the size of the frame image with transparent space around the phone, so it isn't an App Store size and it carries an alpha channel. It suits a website or a press kit, and it won't go into App Store Connect as is. For a one-off framed image of an iPad or iPhone Duo screen, which frameit can't frame, the free device mockup generator exports a PNG in the browser with no account.
For files that upload, add a Framefile.json next to the language folders with a background and a title. In that mode frameit sizes the background to the original capture, shrinks the framed device to fit under the title, and saves a file at the capture's pixel size. Titles come from title.strings in each language folder (keyed by a part of the file name, such as "01-home" = "Plan your week in a minute";), and frameit leaves any screenshot without a matching title unframed. Two current open issues are worth knowing before you commit to it: frameit can take a long time per file on large runs (#30462), and titles don't line up vertically across slides of different lengths (#30430).
Raw captures are valid App Store screenshots, so framing is optional, and the device list decides whether it's safe. frameit logs an error for each size it can't frame and leaves that capture raw in the same language folder, next to the framed iPhone files. deliver then uploads only the _framed files from that folder, so your iPad and iPhone Air screenshots never reach App Store Connect. If your Snapfile includes the 13-inch iPad, the iPhone Air, or iPhone Duo, leave frameit out of that run. Frame only when every device you capture has a frame and a title.
Uploading with deliver
deliver reads fastlane/screenshots/<locale>/ and sorts each file into a display set by its pixel size, so the folder doesn't need device subfolders. Use an App Store Connect API key for auth (a .p8 key described in a JSON file and passed as api_key_path) so the upload doesn't stop for a two-factor prompt. A screenshots-only upload looks like this:
upload_to_app_store(
api_key_path: "fastlane/asc_api_key.json",
skip_binary_upload: true,
skip_metadata: true,
overwrite_screenshots: true,
force: true
)
overwrite_screenshots is broader than its name suggests. For every language that has a folder, deliver deletes all of that language's screenshot sets before uploading, including device sets you didn't capture this time. If your en-US folder holds only iPhone files, the en-US iPad screenshots are gone after the run, so either keep every device's files in the folder or leave the option off and let deliver add files beside the old ones. Each set holds up to 10 screenshots, and deliver skips anything past the tenth. When any _framed file exists in a folder, deliver uploads only the framed files and skips the raw ones, which is easy to miss if frameit skipped a slide for lack of a title.
deliver uploads to the app version you can still edit, and submit_for_review defaults to false, so the screenshots wait there until you submit. Once a version is live its screenshots are locked, and the guide to changing screenshots after release covers creating the next version to hold them.
One lane for capture and upload
lane :screenshots do
capture_screenshots
upload_to_app_store(
api_key_path: "fastlane/asc_api_key.json",
skip_binary_upload: true,
skip_metadata: true,
force: true
)
end
Run it with bundle exec fastlane screenshots. The lane leaves out frame_screenshots on purpose; add it between the two steps only once a Framefile.json is in place and every device in the Snapfile has a frame. On a first run, stop before the upload and open a few files from each language folder in the screenshot size checker. It takes up to 10 PNG or JPEG files at a time, checks them in the browser against Apple's accepted sizes, and flags transparency, so a file from the wrong simulator turns up before App Store Connect rejects it. It won't resize anything. For a long run, concurrent_simulators (on by default) boots devices in parallel, and if one device fails, an open issue reports that snapshot retries the devices that passed as well (#30464).
Android: screengrab and supply
Google Play uses a parallel pair. screengrab runs your instrumentation tests on an emulator or a connected device, saves an image wherever a test calls Screengrab.screenshot("name"), and needs the paths to both the app APK and the test APK. supply uploads from fastlane/metadata/android/<locale>/images/, with separate folders such as phoneScreenshots and tenInchScreenshots. frameit can frame Android captures with fastlane frameit android, though its newest Pixel frame is the Pixel 5. Play's accepted dimensions are listed on the Google Play screenshot sizes page.
Using snapshot output with Screenies
A snapshot folder works as source material for Screenies, which accepts PNG, JPEG, or WebP captures up to 10 MB each and returns a designed set with headlines and backgrounds, including the 13-inch iPad and iPhone Duo sizes that frameit can't frame. Clean mode keeps each capture upright and straight-on in a device frame, and the exported files (1290 x 2796 for iPhone, 2064 x 2752 for iPad) fall into the same deliver sets as your raw captures, so they can go back into fastlane/screenshots/<locale>/ for the upload step. Clear the raw captures and any _framed files out of that folder first, since deliver skips unframed files when framed ones exist and counts every file in a set toward Apple's 10. Phone slides cost one credit and tablet slides two, as listed on the pricing page.
FAQ
Why are my snapshot screenshots in the wrong language?
Check that setupSnapshot(app) runs before app.launch(), since that's where the language arguments are added. If your app reads its language from the system instead of launch arguments, add localize_simulator(true) to the Snapfile, which sets the simulator's own system language for each run.
Can deliver upload screenshots without uploading a build?
Pass skip_binary_upload: true and skip_metadata: true, and deliver updates only the screenshots on the version you can edit. It doesn't submit anything unless you set submit_for_review: true.
Does fastlane snapshot work with SwiftUI apps?
It drives the app through XCUITest, which finds SwiftUI views by their accessibility labels and identifiers. Adding .accessibilityIdentifier("plans-tab") to the views your test taps keeps the test working when button text changes between languages.
Is fastlane free?
It's free and open source under the MIT license, and you install it as a Ruby gem, usually through a Gemfile so the whole team runs the same version with bundle exec.
How do I get iPad screenshots if frameit can't frame the 13-inch size?
Upload the raw 2064 x 2752 captures, which App Store Connect accepts as they are, or design them in another tool. The guide to iPad screenshots without an iPad covers which iPad simulators fill the 13-inch slot.
Related guides

Google Play Feature Graphic: Size, Placement, and How to Make One
How to make the 1024 x 500 Google Play feature graphic, where Google shows it, how to keep your logo and app name out of the cutoff zones and the video play button, and how to check the file before Play Console sees it.

App Store Screenshots From the Simulator: Devices, Sizes, and a Capture Script
Which Xcode Simulator devices produce a screenshot App Store Connect accepts as is, a short script that tidies the status bar and saves numbered captures, and what gets a Simulator screenshot rejected.

App Store Landscape Screenshots: When to Use Them and How to Make Them
How to decide whether your App Store listing should use landscape screenshots, the landscape sizes Apple accepts for iPhone and iPad, how to capture wide screens in the Simulator, and how to lay out a slide that's wider than it is tall.