Every locale, every device, is another stack of PNGs#
Open App Store Connect and it is the screenshots that are mandatory, not the preview video. Apple lets you skip the video; it will not let you publish a listing with an empty screenshot slot. Each supported device family requires at least one screenshot and accepts up to ten per localization, as PNG or JPEG with no alpha channel1. Metadata now localizes into 50 languages and locales2 — Apple added 11 of them in March 2026, most Indian languages3. Multiply those two numbers and the "screenshots" line item stops being an afternoon and becomes a spreadsheet.
The current rules do grant one mercy. You no longer author a bespoke set for every screen size in Apple's back catalog. Provide the largest display in each family — the 6.9-inch iPhone at 1320 x 2868 and the 13-inch iPad at 2064 x 2752 — and the store scales those down to fill the 6.5-inch, 6.3-inch, and 11-inch slots for you1. Device sizes cap at two. Locales do not.
Here is the matrix a real listing produces at eight shots per device, a normal marketing set well under the ten-shot ceiling:
| Listing | Shots | Device families | Locales | Total PNGs |
|---|---|---|---|---|
| iPhone-only, English plus four | 8 | 1 | 5 | 40 |
| Universal, 12 locales | 8 | 2 | 12 | 192 |
| Universal, 25 locales | 8 | 2 | 25 | 400 |
| Universal, every locale at the ceiling | 10 | 2 | 50 | 1,000 |
Add Google Play, which wants two to eight phone screenshots plus a tablet set, each sized between 320 and 3,840 px8, and the cross-store count roughly doubles. Nobody hand-produces four hundred pixel-exact PNGs once, and Apple's metadata-accuracy expectations mean you produce them again whenever the UI they show changes. The moving-picture cousin of this problem, the app preview video, has its own spec sheet; this is the still-frame half, and it is the taller stack.
fastlane snapshot: your UI tests drive the camera#
fastlane is a Ruby toolkit for iOS and Android release automation, MIT-licensed7. Its snapshot action solves capture by reusing something your project may already own: a UI-test target. Rather than script a separate screenshot bot, you drop snapshot("01LoginScreen") at the point in an XCUITest where the screen you want is on-screen4. A UI test cannot render a window directly, so under the hood snapshot triggers a device rotation to an orientation a user would never pick and lets Xcode's test reporter grab the frame — a hack, but a reliable one.
The fan-out lives in a Snapfile:
devices(["iPhone 16 Pro Max", "iPad Pro 13-inch (M4)"])
languages(["en-US", "de-DE", "ja", "fr-FR"])
scheme("UITests")
override_status_bar(true)
fastlane snapshot runs that test once per device per language, across simulators in parallel on Xcode 9 and later. Two options make the output store-ready rather than merely captured. override_status_bar pins the clean 9:41, full-battery, full-signal bar Apple uses in its own marketing, so the clock in shot one matches the clock in shot two hundred. And launch_arguments lets the test boot the app into a fixed state — a seeded account, a chosen theme — so the pixels are reproducible instead of whatever the demo database happened to hold that morning4.
The quiet win is resolution. A simulator renders at the true device pixel grid: the iPhone 16 Pro Max simulator produces 1320 x 2868, which is exactly the 6.9-inch screenshot spec, with no resize step. That is the reverse of the app preview video, where you downscale to a smaller 886 x 1920 frame — Apple wants screenshots at full native resolution and the video at a reduced one. For stills, the simulator hands you the right number for free.
frameit: the frame and the caption are JSON, not Photoshop#
A raw simulator capture is a bare app screen. The App Store shots that actually convert are usually set in a device bezel under a headline, and producing that by hand is where the localized matrix truly detonates. frameit moves it into config. It wraps each PNG in a device frame and, driven by a Framefile.json, adds a title and keyword line with your fonts, colors, background, and padding5. A default block sets the global look; a data array overrides per screenshot by filename filter.
The captions localize the way the app already does. frameit reads title.strings and keyword.strings files — ordinary iOS .strings files, one folder per locale — so the German headline for shot three comes from de-DE/title.strings and the Japanese one from ja/title.strings, fed by the same translation workflow you use for in-app copy5. The framed, captioned, localized set is now a function of a JSON file plus per-language strings, which is the property that makes it regenerate: change the headline and re-run, instead of reopening forty layered documents. deliver closes the chain by uploading everything in fastlane/screenshots to App Store Connect6, so snapshot then frameit then deliver is capture, dress, and publish behind a single CI job.
The screenshots break exactly when the UI tests do#
This pipeline earns the candor the pillar on regenerating media instead of re-recording it asks for. Its cost is the UI test. snapshot only captures a screen the test can reach, so every shot rides on a test that taps through the app by accessibility identifier — and a redesign that renames or moves those controls breaks the capture before it breaks anything a user sees. That is the same UI drift that quietly dates every screenshot, except it surfaces as a failing test rather than a stale image, which is the better place to catch it. Simulators are slow, too; parallelism helps, but a fifty-locale run is a real slice of CI minutes, and the whole thing assumes macOS, Xcode, and a maintained test target. None of that is free. It is only cheaper than the alternative, which is letting the images rot until a customer notices.
When the browser beats the simulator as the camera#
The simulator path is the right one for a genuinely native app, and it would be dishonest to claim otherwise: a browser cannot render authentic iOS chrome, and an App Store shot of a native app should show the native UI. But a large share of "apps" are web underneath — a PWA, a responsive web build, a Capacitor or web-view wrapper, a cross-platform surface that paints HTML. For those, booting a simulator is a heavy way to photograph a web page, and a browser-capture pipeline wins on the axes that matter:
| Situation | Reach for | Why |
|---|---|---|
| Native iOS/Android UI | Simulator (fastlane snapshot) | Only the simulator or device renders authentic native chrome at the exact device grid |
| PWA, responsive web, web view | Browser capture | The store screen is a web page; a headless browser pins the viewport and re-renders on change |
| No macOS or Xcode in CI | Browser capture | The simulator path needs the Apple toolchain; a browser runs on any runner |
| One set for store, docs, and web | Browser capture | A single deterministic capture feeds every surface instead of two pipelines |
A browser path still owes you frames and captions — the frameit job — but you add those as a compositing pass from the same JSON. Our own engine, aidemo, takes the browser route: an agent writes the storyboard and a deterministic replay records the web UI. The disclosures that matter here: it is ours, it photographs a browser and nothing native (a real iOS binary stays out of reach), the storyboard is code a coding agent produces instead of a timeline anyone drags, and it ships no visual editor. The tool matters less than the boundary it draws: pick the camera by what the product actually is. If the store screen is native, let the simulator shoot it; if it is a web page wearing an app icon, do not boot a phone to take its picture.
Sources#
- Apple — App Store screenshot specifications
- Apple — App Store localizations (metadata languages)
- Apple — App Store adds 11 languages for localized metadata (March 2026)
- fastlane snapshot action docs
- fastlane frameit action docs
- fastlane iOS screenshots getting-started guide
- fastlane repository (MIT license)
- Google Play — screenshot and graphic asset requirements
FAQ#
How do I automate App Store screenshots?#
The standard iOS pipeline is three fastlane commands. snapshot captures them by running your XCUITest target once per device and language listed in the Snapfile, capturing wherever you call snapshot("name"). frameit wraps each capture in a device frame and adds a localized title from a Framefile.json plus per-locale .strings files. deliver uploads the finished set to App Store Connect. All three are MIT-licensed and driven by config, so a UI change means re-running the job rather than reshooting by hand.
How many App Store screenshots do I actually need?#
At minimum one, for the largest display in each device family your app supports — a 6.9-inch iPhone shot, plus a 13-inch iPad shot if the app is universal — because Apple scales those down to every smaller size automatically. The ceiling is ten per device family per localization. A typical marketing listing uses five to eight, and with up to 50 metadata localizations available, that single choice decides whether you are maintaining dozens of PNGs or hundreds.
Do I need a Mac and Xcode to use fastlane snapshot?#
Yes. snapshot drives xcodebuild and the iOS Simulator, so it needs macOS, Xcode, and a maintained UI-test target; there is no headless-Linux shortcut for capturing native iOS chrome. If your app's store surface is actually a web page — a PWA or a web-view wrapper — you can skip the Apple toolchain and drive a headless browser instead, which runs on any CI runner and produces the same PNGs.



