10 KiB
| name | description |
|---|---|
| store-screenshots | Produce store screenshots and the Play feature graphic for a mobile game in every required size and many languages - capture raw frames from the running game (web build via playwright, iOS Simulator via simctl, Android emulator or device via adb, or a folder of frames from any engine), write per-language marketing copy (incl. RTL Arabic and CJK fonts), render captioned scenes with device frames, review them in a local gallery, A/B variants for store listing experiments / Product Page Optimization, JPEG copies for Play, and upload to both stores. Use when the user needs App Store / Google Play screenshots, preview images, a feature graphic, localized screenshot captions, or screenshot sizes. |
Store screenshots
CLI: storebound (or bunx storebound in a project that has it, or bun <toolchain>/cli/index.ts). Needs ffmpeg, and playwright in the project
(bun add -d playwright; it uses Playwright's Chromium or an installed Google Chrome) for rendering and web capture.
iOS Simulator capture needs macOS + Xcode (xcrun); Android capture needs adb (platform-tools).
Sizes (what each store wants)
| size key | pixels | where |
|---|---|---|
| ios69 | 1320 x 2868 | App Store 6.9" (APP_IPHONE_67), required |
| ios65 | 1284 x 2778 | App Store 6.5" (APP_IPHONE_65) |
| ipad13 | 2064 x 2752 | App Store 13" iPad (APP_IPAD_PRO_3GEN_129), required when the app runs on iPad; only with apple.ipad: true |
| play | 1080 x 1920 | Play phone (2-8, ratio at most 2:1) |
| tab7 | 1080 x 1920 | Play 7" tablet (tablet frames, Android tablet body) |
| tab10 | 1440 x 2560 | Play 10" tablet |
| feature | 1024 x 500 | Play feature graphic (required) |
Sources (re-check before a release; Apple renames display classes often):
- Apple "Screenshot specifications": https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications (13" iPad: 2064 x 2752 or 2048 x 2732, landscape too, "Required if app runs on iPad"; 1-10 per size; JPEG / PNG, no alpha or transparency).
- ASC API
ScreenshotDisplayType: https://developer.apple.com/documentation/appstoreconnectapi/screenshotdisplaytype (there is no "13 inch" value: the 13" iPad set isAPP_IPAD_PRO_3GEN_129). - Play graphics: https://support.google.com/googleplay/android-developer/answer/9866151 (screenshots 320-3840 px, at most 2:1, JPEG or 24-bit PNG, 8 MB; feature graphic 1024 x 500).
iPad. Set apple.ipad: true only if the app runs on iPad (iPhone-only apps skip the iPad set). Then the default
sizes include ipad13 (3:4 layout), apple screenshots uploads it, validate requires it, and tablet frames default
to the 13" iPad screen (1032 x 1352 CSS px @2x below a 24 px bar), so an iPad Pro 13-inch / iPad Air 13-inch
simulator screenshot fits after the bar is cropped. Play tablet sizes (tab7 / tab10) reuse the same tablet frames, drawn
in a plain Android tablet body instead of the iPad one.
Steps
- Game switches. Give the game URL params for screenshots: language, fixed seed, no ads / analytics / purchase UI, skip tutorial, jump to a level, freeze at a moment. Frames must show real gameplay (store rule).
- Capture. Configure
shots.capturewith asource(or pass--source), thenstorebound shots capture --lang en --dryto see the plan, and without--dryto capture. Frames land in<shots.frames>/<lang>/<phone|tablet>/<shot>.png, local, not committed.web(default):baseUrl,querywith{lang}, shots:path/waitFor/eval/delayMs/device. Phone 440 x 902 @3x and tablet 800 x 1240 @2x by default; the template draws the status bar above.ios-sim(Unity, Godot, Flutter, native, Capacitor: any Simulator build):ios.device(simulator name or UDID, e.g. a 6.9" iPhone),ios.bundleId, optionalios.app(.app to install),ios.args. Per shot:args(launch arguments, read them with UserDefaults / your engine) and / orurl(deep link). Language goes in as-AppleLanguages/-AppleLocale; status bar is forced to 9:41, full battery. After a cold boot the app is launched once and givenios.warmupMs(default 8000) before the first shot; without it the first frame of a fresh simulator is often blank. A simulator storebound boots is shut down afterwards (--keep-bootedkeeps it). For iPad shots setios.devices.tablet(e.g. "iPad Pro 13-inch (M5)") anddevice: 'tablet' | 'both'per shot.android-emu:android.package, optionalserial,activity,apk. Per shot:url(VIEW intent) and / or typedextras. Demo-mode status bar; per-app language on Android 13+ (older: pass{lang}in an extra). Nothing is wiped or rebooted.folder: frames made any other way;shots capture(andshots check) only verify that every frame the scenes use exists with the right aspect ratio and resolution.{lang},{locale},{shot}are replaced in args, extras and URLs. Simulator / emulator screenshots losecropToprows (the status bar) so they fit the template: by default the frame's bar share per device class (162 px on a 6.9" iPhone, 48 px on a 13" iPad withapple.ipad); setcropTop: 120or{ phone, tablet }for other devices, andshots.phone/shots.tabletto that device's screen in points. Capture warns when a frame's aspect does not match. Give the game time to load:delayMs(simulator / emulator; 2500 default is short for a cold start) orwaitFor(web).
- Copy.
store/shots/copy.json: per language, per scene:eyebrow,title[white line, gold line],sub,badgesbig, small. Short and true (only claim what is in the game); about 15 Latin characters per title line. Natural marketing tone per language, not literal translation; use the game's own terms. - Fonts.
storebound shots fontsdownloads title/body fonts and a Noto subset for every non-Latin script found in the copy (CJK, Arabic, Hebrew, Devanagari, Thai, Cyrillic/Greek). Re-run after copy edits. - Scenes.
shots.scenes: palette (fire, violet, blast, green, night, sky, gold, candy or custom), frames (one front phone + optional back phones with x / dy / scale / rotate), badge slots (y, side, icon, hot, stars). - Render.
storebound shots render --lang enfirst, reviewstore/renders/en/sheet.png(contact sheet, sizes packed into a grid), then all languages in the background:storebound shots render > .storebound/render.log 2>&1 &. - JPEG for Play.
storebound shots jpeg(q92, about 4x smaller than PNG; App Store stays PNG). - Check.
storebound shots check(read-only, exit 1 on errors): pixel sizes (Apple: a listed size, portrait or landscape; Play screenshots 320-3840 px at most 2:1; feature graphic exactly 1024 x 500), alpha, Play JPEG / 24-bit PNG and 8 MB, counts per set (Apple 1-10, Play 2-8), text overflow flags (warnings).--variantsalso checks A/B variant folders.storebound validateruns the same checks on the renders folder and lists errors as blockers next to the store state (--no-localskips them). - Review.
storebound shots gallerywrites<renders>/index.html: one self-contained page (no network, open it from disk) with every language x size x scene, app previews with posters, variant tabs, filters (language, size, only issues, search) and light / dark. Tiles with store check errors are outlined red, warnings amber, with the message under the tile. Show the owner this page (or a screenshot of it) before uploading. - Upload.
storebound play imagesandstorebound apple screenshots(both idempotent, background + log).
A/B variants and store experiments
shots.variants.<id>: scenes (order / subset), copy (a JSON merged over the base copy, only what changes),
palettes (per scene). storebound shots render --variant b (or --variants for all) writes
<renders>/b/<lang>/...; shots check --variants, shots gallery (tabs) and shots jpeg --variant b cover them.
play images --variant b / apple screenshots --variant b upload a variant to the MAIN listing (replacing the base
images there), so only do that after a test has a winner. What the stores offer, honestly:
- Google Play store listing experiments (Play Console > Grow users > Store listing experiments): up to 2 variants against the current listing; one default-graphics experiment or up to 5 localized experiments at a time; icon, feature graphic, screenshots (+ descriptions for localized ones); stops automatically after 6 months. Console only: the Play Developer API has no experiments endpoint, so storebound renders and checks the variant images and you upload them in the experiment form yourself. https://support.google.com/googleplay/android-developer/answer/6227309
- App Store Product Page Optimization (App Store Connect > your app > Product Page Optimization): up to 3
treatments against the original, one test at a time, up to 90 days; icon, screenshots and app previews, localized
per treatment; new metadata goes through App Review; alternate icons must ship in the binary; shown on iOS / iPadOS
15+. https://developer.apple.com/app-store/product-page-optimization/
The App Store Connect API has the resources (
appStoreVersionExperimentsv2, treatments, treatment localizations with their own screenshot / preview sets), but storebound does not create or start experiments: set the test up in App Store Connect and upload the treatment screenshots there (the variant folder already has the right sizes). - Test one thing per experiment (usually the first screenshot or the order), give it real traffic, and do not read results early.
Gotchas
- Output is 24-bit RGB PNG: Play rejects alpha / palette PNGs, Apple rejects alpha.
- Arabic:
rtl: trueon the language; numbers stay LTR inside RTL text automatically. - Japanese breaks by phrase, Korean at spaces; long words shrink the line rather than wrap.
- Check every language's sheet for overflow before uploading; German / Russian titles run long.
- Play: 8 per type max; Apple: 10 per set max.
- Do not include prices, "free" badges for paid items, or other platforms' UI.