Upload Screenshots with fastlane deliver & supply
Automate App Store and Google Play screenshot and metadata uploads with fastlane deliver and supply: folder structure, file naming, locale codes and CI tips.
fastlane’s deliver uploads App Store screenshots from fastlane/screenshots/<locale>/ and metadata from fastlane/metadata/<locale>/*.txt; supply does the same for Google Play from fastlane/metadata/android/<locale>/. Put files in the right folders, authenticate with an API key or service account, and one command replaces every screenshot in every language — locally or on CI.
deliver and supply in one minute
| deliver (App Store) | supply (Google Play) | |
|---|---|---|
| Screenshots | fastlane/screenshots/<locale>/ | fastlane/metadata/android/<locale>/images/<type>Screenshots/ |
| Metadata | fastlane/metadata/<locale>/*.txt | fastlane/metadata/android/<locale>/*.txt |
| Device type | Detected from image resolution | Chosen by folder name |
| Auth | App Store Connect API key | Google Cloud service account JSON key |
| Skip binary | --skip_binary_upload | --skip_upload_apk --skip_upload_aab |
Both tools can also download what’s currently live: fastlane deliver download_screenshots and fastlane deliver download_metadata for the App Store, and fastlane supply init for Google Play. Doing that once is the easiest way to see the expected layout for your own app.
App Store: deliver folder structure
fastlane/
├── screenshots/
│ ├── en-US/
│ │ ├── 01_iPhone 6.3in.png # 1206 × 2622
│ │ ├── 01_iPad 13in.png # 2064 × 2752
│ │ ├── 02_iPhone 6.3in.png
│ │ └── ...
│ └── de-DE/
│ └── ...
└── metadata/
├── copyright.txt
├── primary_category.txt
├── en-US/
│ ├── name.txt
│ ├── subtitle.txt
│ ├── keywords.txt
│ ├── description.txt
│ ├── promotional_text.txt
│ ├── release_notes.txt
│ ├── privacy_url.txt
│ ├── support_url.txt
│ └── marketing_url.txt
└── de-DE/
└── ...- Device detection: deliver works out the display class (6.9″ iPhone, 6.3″ iPhone, 13″ iPad, …) from each image’s exact pixel size, so the sizes must be ones App Store Connect accepts — see the screenshot sizes guide.
- Order: give files a numeric prefix (
01_,02_) so each device’s set sorts in the order you want to show. - Limit: up to 10 screenshots per display class and locale.
- Metadata files are plain UTF-8 text, one field per file. Stay within the character limits — keywords are 100 characters, comma-separated.
- The
defaultfolder: values infastlane/metadata/default/apply to every language that doesn’t provide its own file.
APP_IPAD_PRO_129 in the file name.Google Play: supply folder structure
fastlane/metadata/android/
├── en-US/
│ ├── title.txt
│ ├── short_description.txt
│ ├── full_description.txt
│ ├── video.txt
│ ├── changelogs/
│ │ ├── 10203.txt # matches the versionCode
│ │ └── default.txt # used when no versionCode file exists
│ └── images/
│ ├── icon.png
│ ├── featureGraphic.png
│ ├── tvBanner.png
│ ├── phoneScreenshots/
│ │ ├── 1_en-US.png
│ │ └── 2_en-US.png
│ ├── sevenInchScreenshots/
│ ├── tenInchScreenshots/
│ ├── tvScreenshots/
│ └── wearScreenshots/
└── de-DE/
└── ...- Folder = device type. Unlike deliver, supply doesn’t inspect resolution; whatever is in
phoneScreenshotsbecomes your phone set. - Replace, not append. Uploaded images replace the current images on the listing.
- Order is alphanumerical by file name. Note that
10_sorts before2_; with up to 8 screenshots per type that is rarely a problem, but zero-padding is safest. - Feature graphic must be 1024 × 500 — see the feature graphic guide.
- Set
sync_image_uploadto skip images that haven’t changed (compared by checksum).
Locale codes differ between stores
App Store Connect and Google Play don’t use the same codes, so the same language can need differently named folders:
| Language | App Store (deliver) | Google Play (supply) |
|---|---|---|
| English (US) | en-US | en-US |
| German | de-DE | de-DE |
| Italian | it | it-IT |
| Japanese | ja | ja-JP |
| Korean | ko | ko-KR |
| Spanish (Mexico / Latin America) | es-MX | es-419 |
| Chinese (Simplified) | zh-Hans | zh-CN |
| Hebrew | he | iw-IL |
A folder with a code the store doesn’t recognize is either rejected or ignored, so check the names against each store’s list. The localization guide has the full mapping.
Authentication
App Store Connect API key
Create a key under Users and Access › Integrations in App Store Connect (App Manager access is enough for metadata) and download the .p8 file. Either use the app_store_connect_api_key action in your Fastfile, or save a JSON file and pass --api_key_path:
{
"key_id": "ABC123XYZ9",
"issuer_id": "00000000-0000-0000-0000-000000000000",
"key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
"in_house": false
}Google Play service account
In Google Cloud, create a service account, enable the Google Play Android Developer API and download a JSON key. Then invite the service account’s email in Play Console under Users and permissions with permission to edit the store listing. Check the key with fastlane run validate_play_store_json_key json_key:path/to/key.json.
A Fastfile for store assets
This lane uploads screenshots and metadata only — no build, no submission:
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Upload App Store screenshots and metadata (no binary)"
lane :store_assets do
api_key = app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"],
issuer_id: ENV["ASC_ISSUER_ID"],
key_content: ENV["ASC_KEY_P8_BASE64"],
is_key_content_base64: true
)
deliver(
api_key: api_key,
app_identifier: "com.example.myapp",
skip_binary_upload: true,
overwrite_screenshots: true,
submit_for_review: false,
force: true, # skip the HTML preview confirmation
precheck_include_in_app_purchases: false
)
end
end
platform :android do
desc "Upload Google Play screenshots and listing (no binary)"
lane :store_assets do
supply(
package_name: "com.example.myapp",
json_key_data: ENV["PLAY_SERVICE_ACCOUNT_JSON"],
skip_upload_apk: true,
skip_upload_aab: true,
skip_upload_changelogs: true
)
end
endRun it with fastlane ios store_assets or fastlane android store_assets. The one-off command-line equivalents:
fastlane deliver --skip_binary_upload --overwrite_screenshots --api_key_path asc_key.json
fastlane supply --skip_upload_apk --skip_upload_aab --json_key play_key.json --package_name com.example.myappdeliver writes to the App Store version that is currently editable. Create the new version in App Store Connect first, or pass app_version. Changelogs are skipped above because Play release notes belong to a release; upload them with your build instead.
Running it in GitHub Actions
Add gem "fastlane" to a Gemfile, commit Gemfile.lock, and add the four secrets used below. This workflow runs on demand:
# .github/workflows/store-assets.yml
name: Upload store assets
on: workflow_dispatch
jobs:
app-store:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- run: bundle exec fastlane ios store_assets
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
google-play:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- run: bundle exec fastlane android store_assets
env:
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}Create the base64 secret with base64 -i AuthKey_ABC123XYZ9.p8 (macOS) and paste the output; paste the service account JSON as-is.
Common pitfalls
- “Unknown device” or skipped screenshots. The image isn’t an exact accepted size, or your fastlane version predates a newer display class such as 1206 × 2622. Update fastlane and verify sizes with the screenshot checker.
- Alpha channel. App Store Connect rejects PNGs with transparency; flatten them with the resizer. See fixing the dimensions error.
- Screenshots added instead of replaced. Without
overwrite_screenshots, deliver keeps existing screenshots and you can hit the 10-per-class limit. - Wrong locale folder names — especially
javsja-JPandzh-Hansvszh-CNbetween the two stores. - Precheck failing with an API key. When deliver runs precheck (before submitting for review), its in-app purchase check doesn’t work with an API key; set
precheck_include_in_app_purchasesto false. - Header, search and in-app event assets aren’t part of the deliver screenshot folders — upload them in App Store Connect.
With our tools
When you export a ZIP from the editor with the fastlane naming scheme, it is laid out for both tools: App Store screenshots in fastlane/screenshots/<locale>/NN_<size>.png, App Store metadata in fastlane/metadata/<locale>/*.txt, and Google Play images in fastlane/metadata/android/<locale>/images/<folder>/ with the feature graphic and icon alongside, plus Play listing text and a changelogs/default.txt when you include metadata. Locale folders use each store’s own codes. Unzip it into your project root, review the diff, and run your lane. Assets deliver doesn’t handle — header, search and event artwork — land in a separate store-assets/ folder for manual upload.
Frequently asked questions
Where does fastlane deliver look for screenshots?
In fastlane/screenshots, with one subfolder per App Store locale such as en-US or de-DE. deliver detects the device class from each image’s pixel size, so all iPhone and iPad sizes for a language can share one folder.
How do I upload only screenshots and metadata with fastlane deliver?
Run fastlane deliver --skip_binary_upload --overwrite_screenshots, or set skip_binary_upload and overwrite_screenshots to true in the deliver action. overwrite_screenshots removes the existing screenshots before uploading the new ones.
Where does fastlane supply look for Google Play screenshots?
In fastlane/metadata/android/<locale>/images/, in folders named phoneScreenshots, sevenInchScreenshots, tenInchScreenshots, tvScreenshots and wearScreenshots. The feature graphic and icon go directly in images as featureGraphic.png and icon.png.
How do I control the screenshot order in fastlane?
Name the files so they sort in the order you want, for example with a 01_, 02_ prefix. supply uploads screenshots in alphanumerical filename order and replaces the existing set rather than adding to it.
How does fastlane authenticate with App Store Connect on CI?
Use an App Store Connect API key: pass it with the app_store_connect_api_key action or point deliver at a JSON key file with api_key_path. This avoids Apple ID passwords and two-factor prompts.
What credentials does fastlane supply need?
A JSON key for a Google Cloud service account that has been granted access to your app in Play Console. Pass the file path with json_key or the file content with json_key_data.