skip to content

In fastlane, how do captured screenshots and localized metadata reach the upload actions on iOS and Android?

level: seniorimportance: should knowfreq 44%

answer

  1. the filesystem is the contract
  2. one subdirectory per locale
  3. two roots one side, one tree the other
  4. artwork can ship without a binary

basics

~20 s

Through directories on disk, not return values. Capture writes one folder per locale under output_directory; on Apple, upload_to_app_store reads a separate screenshots_path and metadata_path, while on Google Play upload_to_play_store reads a single metadata_path tree that already contains the images.

solid answer

~30 s

The join between capture and upload is the filesystem. `capture_ios_screenshots` and `capture_android_screenshots` each write one subdirectory per locale under `output_directory`, and the upload action is pointed at that tree. The shapes differ. **`upload_to_app_store`** (`deliver`) takes **two** roots: `screenshots_path` for artwork and `metadata_path` for the per-locale text files whose names are the metadata keys — `description`, `keywords`, `subtitle`, `promotional_text`, `release_notes`. **`upload_to_play_store`** (`supply`) takes **only** `metadata_path`, because Google Play's listing images live inside that same tree; it compensates with per-section switches like `skip_upload_images`, `skip_upload_screenshots` and `skip_upload_changelogs`. Both let you push artwork with no binary at all.

code

ruby · 20 lines
ruby
lane :refresh_ios_listing do
  capture_ios_screenshots
  frame_screenshots
  upload_to_app_store(
    screenshots_path: 'fastlane/screenshots',
    metadata_path: 'fastlane/metadata',
    overwrite_screenshots: true,
    skip_binary_upload: true
  )
end

lane :refresh_android_listing do
  capture_android_screenshots
  upload_to_play_store(
    metadata_path: 'fastlane/metadata/android',
    skip_upload_apk: true,
    skip_upload_aab: true,
    skip_upload_changelogs: true
  )
end

go deeper

for a junior

Recall that capture writes one folder per locale and the upload action is pointed at that folder with a path option. Nothing is passed between the two actions in memory.

for a middle

Explain the structural difference: a separate screenshots_path and metadata_path on the Apple side versus one metadata tree with per-section skip switches on the Google Play side.

for a senior

Show you can run artwork-only updates, keep capture and upload on different machines through a published directory, and stop stale locales reaching a listing after the locale set changes.

for a principal

Own who edits listing copy and how it is reviewed. Weigh metadata-as-files in the repository against a marketing-owned console workflow, and decide where the two stores' divergent credentials are held.

## The contract is a directory tree, not a return value Nothing is handed from a capture action to an upload action in memory. `capture_ios_screenshots` and `capture_android_screenshots` **write files**, the upload action **reads files**, and `output_directory` on one side plus the path options on the other are the entire interface. Understanding that is what lets you frame images in between, capture on one machine and upload from another, or refresh a listing's artwork without rebuilding anything. Both capture actions produce **one subdirectory per locale**. That per-locale shape is exactly what each store's listing needs, which is why capture and upload compose without glue code. ## The Apple side `upload_to_app_store` — the canonical name behind **`deliver`** — takes **two separate roots**: - `screenshots_path` points at the captured artwork, conventionally the `fastlane/screenshots` tree that `capture_ios_screenshots` wrote. - `metadata_path` points at the localized text. Inside a locale's directory each piece of copy is its own file, named for the metadata key it carries: `description`, `keywords`, `name`, `subtitle`, `promotional_text`, `release_notes`, `support_url`, `marketing_url`, `privacy_url` and `copyright` are all real keys the action understands. It also carries screenshot-specific behaviour switches: - `overwrite_screenshots` clears what the listing already has before uploading, so a removed locale or device size does not linger. - `sync_screenshots` uploads only what actually changed rather than the whole set. - `skip_screenshots`, `skip_metadata` and `skip_binary_upload` let one lane push exactly one of the three payloads. ## The Google side `upload_to_play_store` — the canonical name behind **`supply`** — takes **only `metadata_path`**. There is no separate screenshots path, because Google Play's listing images live *inside* the metadata tree, keyed by locale and by image slot. That is why `capture_android_screenshots` defaults its `output_directory` underneath the Android metadata tree rather than to a sibling screenshots folder: capture writes directly into the shape supply reads. Because everything is one tree, the granularity moved into the switches: - `skip_upload_metadata` — leave the listing text alone. - `skip_upload_images` — leave the fixed listing graphics alone. - `skip_upload_screenshots` — leave the per-device screenshot sets alone. - `skip_upload_changelogs` — leave the release-note files alone. - `sync_image_upload` — reconcile what is on the listing with what is on disk instead of blindly re-uploading everything. - `skip_upload_apk` and `skip_upload_aab` — send listing assets with no binary at all. ## The two shapes side by side | | Apple (`upload_to_app_store`) | Google (`upload_to_play_store`) | |---|---|---| | screenshot root | `screenshots_path`, separate from metadata | inside the `metadata_path` tree | | capture default output | conventionally a `fastlane/screenshots` tree | under the Android metadata tree | | skip artwork only | `skip_screenshots` | `skip_upload_images`, `skip_upload_screenshots` | | replace vs reconcile | `overwrite_screenshots`, `sync_screenshots` | `sync_image_upload` | | artwork without a binary | `skip_binary_upload` | `skip_upload_apk`, `skip_upload_aab` | | credential model | App Store Connect API key or Apple ID | Google service-account JSON key | The last row is worth saying out loud, because it is the fact people most often flatten: there is no shared credential model between the two. Apple uses an App Store Connect API key (or an Apple ID username); Google Play uses a service-account JSON key. A lane that captures for both platforms still needs two entirely different secrets to upload the results. ## Why the separation earns its keep - **Artwork-only releases.** New localized screenshots for the sailmaker measurement app can go live with `skip_binary_upload` on Apple, or `skip_upload_apk` plus `skip_upload_aab` on Google, so a copy fix does not require a build. - **Framing in the middle.** Because the handoff is a directory, `frame_screenshots` can rewrite the images between the two steps without either action knowing. - **Split machines.** Capture can run where the simulators and devices are and publish the directory as an artifact; upload can run somewhere else entirely against that artifact. - **Reviewable copy.** Metadata as one text file per key per locale means listing copy diffs in a pull request like any other source file. ## Failure modes to name 1. Pointing the upload action at the wrong root, so it finds an empty tree and cheerfully uploads nothing. 2. Forgetting `clear_previous_screenshots` at capture time, so a dropped locale's stale artwork is still on disk and gets uploaded. 3. Assuming Apple's two-root layout applies to Google Play, then wondering why there is no `screenshots_path` on that side. 4. Re-uploading everything every time instead of using the sync options, which makes every listing update a full replacement. 5. Treating the two credential models as interchangeable. ## What interviewers listen for A strong answer says the filesystem is the contract, names both canonical upload actions, and explains the structural difference — two roots on Apple, one tree with per-section switches on Google — rather than reciting option names without the shape behind them.

  • Why does upload_to_play_store have no screenshots_path when upload_to_app_store has one?
    Because Google Play's listing images live inside the metadata tree, keyed by locale and image slot, so one `metadata_path` addresses text and artwork together. Apple keeps them apart, which is why `upload_to_app_store` takes `screenshots_path` and `metadata_path` separately. Google compensates with per-section switches such as `skip_upload_images`, `skip_upload_screenshots` and `skip_upload_changelogs`.
  • How would you push new localized screenshots without shipping a new binary?
    Run capture, then call the upload action with the binary suppressed: `skip_binary_upload` on `upload_to_app_store`, or `skip_upload_apk` together with `skip_upload_aab` on `upload_to_play_store`. Add `overwrite_screenshots` or the sync options so removed locales and device sizes do not linger on the listing from a previous upload.

saying these in an interview costs you the question

  • Thinks the capture action passes images to upload in memory
  • Expects a screenshots_path option on the Google Play upload action
  • Assumes one credential works for both stores
  • Believes artwork changes always require a new binary
  • Never clears stale locales from the capture output directory