Skip to content
All Guides
How-To Guide45 min read

How to Automate App Store Screenshots in CI/CD

A practical end-to-end pipeline for generating App Store and Google Play screenshots from your CI system using Fastlane, GitHub Actions, and Bitrise — with concrete config you can paste into your repo.

Eric Isensee
Eric IsenseeFounder · Last updated May 5, 2026

TL;DR

Capture raw app screenshots in your test runner, send them to the Screenshots.live API with a template ID and locale list, and Fastlane writes the rendered, framed, localized output back into your repo. Fastlane deliver / supply push them to the stores. Whole pipeline runs on a tag push or nightly cron in under five minutes.

Why automate screenshots at all?

Most teams treat App Store and Google Play screenshots as a one-shot, hand-crafted asset: a designer mocks them up in Figma, a marketer drops them into App Store Connect, and nobody touches them again until the next major redesign. That model breaks the moment you ship to more than one locale. With 13 supported locales, three required iPhone sizes, two iPad sizes, plus phone and tablet on Google Play, you are looking at over 100 PNG files for a single release. Multiply by every copy change, every promo, every A/B test variant, and the math stops working.

Automating in CI/CD makes screenshots a build artifact like any other binary. They are versioned, reproducible, and tied to a commit. When marketing changes a caption, the pipeline rebuilds every locale and uploads them — no manual Photoshop sessions, no asset drift between languages, no rejection at submission because someone forgot a 6.7-inch iPhone variant.

What does the pipeline actually look like?

The shape of a clean CI screenshot pipeline is three stages: capture, render, and deliver. Capture is your existing UI test suite — XCUITest on iOS, Espresso on Android, Detox or Maestro for React Native. Render is one Screenshots.live API call per locale that composites your raw frames onto a template you designed once. Deliver is the existing Fastlane deliver and supply actions you already use to push binaries.

The Screenshots.live REST rendering API is the keystone. You send raw screenshots once and get back every device size, every locale, and every variant — fully framed, captioned, and localized. No headless browser, no Puppeteer, no manual resizing.

How do you wire it up with Fastlane?

Fastlane is the canonical iOS / Android release automation tool. We ship an official Fastlane plugin that wraps the rendering API in a single action. Install it from your project root:

terminal
bash
fastlane add_plugin screenshotslive

Then add a lane to your Fastfile that captures, renders, and writes the rendered output:

fastlane/Fastfile
ruby
lane :render_screenshots do
  # 1. Capture raw frames with XCUITest
  capture_screenshots(
    workspace: "MyApp.xcworkspace",
    scheme: "MyAppUITests",
    devices: ["iPhone 16 Pro Max", "iPad Pro 12.9-inch"],
    languages: ["en-US", "de-DE", "es-ES", "fr-FR", "pt-BR"]
  )

  # 2. Send raw frames to Screenshots.live, get framed PNGs back
  screenshotslive_render(
    api_token: ENV["SCREENSHOTSLIVE_API_TOKEN"],
    template_id: "tpl_app_release_v3",
    input_dir: "./fastlane/screenshots",
    output_dir: "./fastlane/rendered",
    locales: ["en", "de", "es", "fr", "pt"],
    devices: ["iphone-6.7", "iphone-6.1", "ipad-12.9"]
  )

  # 3. Push to App Store Connect
  deliver(
    screenshots_path: "./fastlane/rendered",
    skip_binary_upload: true,
    skip_metadata: false
  )
end

The plugin handles uploads, polling for completion, and download in parallel. A typical 5-locale, 3-device render finishes in 60–90 seconds.

How do you run this on GitHub Actions?

Drop the lane into a workflow file. The workflow below runs on every tag push that matches v* and on a nightly schedule, so a manual git tag v1.2.0 && git push --tags is all you need to trigger a fresh render.

.github/workflows/screenshots.yml
yaml
name: Render Store Screenshots

on:
  push:
    tags: ["v*"]
  schedule:
    - cron: "0 4 * * *"  # nightly at 04:00 UTC
  workflow_dispatch:

jobs:
  render:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.2"
          bundler-cache: true

      - name: Render screenshots
        env:
          SCREENSHOTSLIVE_API_TOKEN: ${{ secrets.SCREENSHOTSLIVE_API_TOKEN }}
          FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
          FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
        run: bundle exec fastlane render_screenshots

      - name: Upload rendered artifacts
        uses: actions/upload-artifact@v4
        with:
          name: store-screenshots
          path: fastlane/rendered/

For teams that prefer a GitHub Action without Fastlane, we also publish a standalone GitHub Action that talks to the API directly:

.github/workflows/screenshots-direct.yml
yaml
- uses: Screenshots-Live/render-screenshots-action@v1
  with:
    api-token: ${{ secrets.SCREENSHOTSLIVE_API_TOKEN }}
    template-id: tpl_app_release_v3
    locales: en,de,es,fr,pt,it,nl
    devices: iphone-6.7,iphone-6.1,ipad-12.9
    output-dir: ./screenshots

What about Bitrise?

Bitrise users get the same lane through the existing Fastlane step. Add a Secret called SCREENSHOTSLIVE_API_TOKEN in your Bitrise workspace, then drop this into your bitrise.yml:

bitrise.yml
yaml
workflows:
  render-screenshots:
    steps:
    - activate-ssh-key@4: {}
    - git-clone@8: {}
    - script@1:
        title: Install Fastlane plugins
        inputs:
        - content: |-
            #!/usr/bin/env bash
            bundle install
            bundle exec fastlane add_plugin screenshotslive
    - fastlane@3:
        inputs:
        - lane: render_screenshots
        - work_dir: "$BITRISE_SOURCE_DIR"
    - deploy-to-bitrise-io@2:
        inputs:
        - deploy_path: ./fastlane/rendered

How do you keep build times low?

The slow part of any CI screenshot pipeline is rendering, not uploading. The trick is to cache renders by content hash so unchanged locales reuse prior output. Screenshots.live returns a stable renderHash in the response — use it as the cache key:

  • Template version + locale + variant hash → cache key
  • Rendered PNG bytes → cache value
  • Cache TTL: 30 days, or until template is republished

On GitHub Actions, use actions/cache@v4 with a key derived from your template ID and the locale list. Most release builds skip rendering entirely and just rebuild the locale that actually changed.

How do you validate output before submitting?

Apple and Google both reject screenshots silently when they fail format checks: a PNG with an alpha channel, a JPEG encoded as CMYK, or a 6.7-inch iPhone screenshot one pixel short. Build a 20-line lint step that catches these before submission:

fastlane/lint_screenshots.rb
ruby
require "chunky_png"

Dir.glob("./fastlane/rendered/**/*.png").each do |path|
  png = ChunkyPNG::Image.from_file(path)
  raise "alpha channel: #{path}" if png.metadata["ColorType"] == 6
  raise "too large: #{path}"     if File.size(path) > 30 * 1024 * 1024
  raise "wrong size: #{path}"    if png.width < 1242
end

Should you run it on a schedule?

Yes. A nightly cron means screenshots regenerate any time your template changes — whether the change came from a designer in the editor, a copy update from marketing, or a new locale being added. Without a schedule, store listings drift out of sync with your real product, and someone has to remember to push a fresh build before every submission.

Combine the schedule with a Slack or email alert on failure. A broken render at 04:00 UTC is a notification at 07:00 local — not a release-blocker discovered at 17:00 on the day you wanted to ship.

What about Android, React Native, and Flutter?

The pipeline is platform-agnostic on the rendering side — only the capture stage changes. For Android, swap capture_screenshots for Fastlane screengrab and your Espresso suite. For React Native, use Detox snapshots. For Flutter, use integration_test screenshots. All produce raw PNGs that Screenshots.live treats identically.

Read more in our multi-platform support guide for capture configuration on each stack.

Where do you go from here?

Once your CI pipeline reliably ships screenshots, the next question is which screenshots win. Combine this pipeline with the A/B testing guide to ship variants on a schedule and measure conversion. Pair it with the localization guide to add 30+ locales without 30+ pipelines.

Authoritative references for further reading: Fastlane iOS screenshots docs, Fastlane deliver action, and the official App Store Connect screenshot specifications.

Try it on a real repo

Generate All These Sizes Automatically

Stop resizing screenshots manually. Design one template and render every size, device, and locale with a single API call.

Start Free — Try Screenshots.live