DEV Community

Dan for Expo

Posted on Originally published at expo.dev

How SWSH turned 300 monthly builds into a release pipeline nobody has to babysit

"Expo's ecosystem helped us take human error and effort out of the release process, from a local build to a validated release."

— Nathan Ahn, CTO, SWSH

You've shipped a mobile app before, so you know the drill: cut the builds, wrestle with signing credentials, submit to two different app stores, then hope nothing regressed on the way out. Do that by hand often enough and one of those steps eventually bites you.

SWSH, a social photo-sharing app for shared albums and events, ships across iOS, an iOS App Clip, Android, and the web from a single React Native codebase, and its release process barely needs a person in the loop. The team runs 300 builds a month on its own hardware, never touches signing credentials directly, drives App Store Connect through its API instead of a browser, and validates every release with a home-built end-to-end suite before it goes out.

This post breaks down how that pipeline is put together, piece by piece, using Expo, EAS Build, EAS Submit, EAS Update, and EAS Observe.

SWSH on the app store

One codebase, every target

The web deploys on its own track, so the rest of this is about the three native targets: iOS, the App Clip, and Android. A change starts life as a local build, runs through an end-to-end test suite on iOS simulators and Android emulators, and lands in both app stores with almost no human involvement. Native releases go out about once a week, and the pipeline is what gets them there without anyone standing over it.

The problem: every manual step is a place to break something

Cut a build, handle credentials, submit to App Store Connect and Google Play, confirm nothing regressed. Done by hand, each of those steps is a place a release can quietly go wrong, and the more often you ship, the more those mistakes cost.

SWSH decided to go after the whole chain at once instead of patching individual steps: automate the entire release process so human error has nowhere left to enter. Expo's build and submit tooling gave them that automation out of the box, and a foundation solid enough to build custom pieces on top where they needed more.

Why Expo works as the base for this

Expo gave SWSH one automated path from source to store: builds, credentials, submission, and over-the-air updates, all under the same toolchain.

Continuous Native Generation (expo prebuild) means the team regenerates native iOS and Android projects on demand instead of hand-maintaining Xcode and Gradle files, while still being able to drop into native code when a feature needs it. And because EAS runs the same build tooling whether it's local or in the cloud, SWSH could adopt EAS's orchestration, credential management, and submission logic without giving up control over where the actual compilation happens.

Inside the pipeline

Local builds, run at SWSH's own scale

For most teams, Expo's cloud builds are the obvious call. SWSH is the exception, and the reason is volume: full iOS and Android builds on every pull request that touches native code, plus per-environment release builds, totaling roughly 300 a month. At that volume the economics shift, so SWSH moved compilation onto self-hosted macOS and Android runners with local EAS builds.

This is the detail that's easy to overlook about EAS: because eas build behaves identically whether it runs locally or in Expo's cloud, SWSH kept the orchestration, credential handling, and submission logic exactly as they were, and moved only the compute-heavy compilation step onto hardware it owns.

Credentials nobody on the team has to touch

Signing credentials for both App Store Connect and Google Play live in EAS. The only secret SWSH's CI holds is a single Expo access token. Code signing is the part of a mobile release you least want sitting in a CI config or a repo, and for SWSH it just isn't there.

Submission across both stores, with a custom layer for iOS

Agentic Release pipeline

EAS Submit pushes each build directly to Google Play and App Store Connect with no custom code needed. On Google Play, that's the entire job. iOS asks for more work around the binary: a new App Store version, metadata and release notes, a configured App Clip experience, and a submission for review.

So SWSH pairs EAS Submit with an App Store Connect pipeline it built itself. Once EAS Submit uploads the build, that pipeline talks to the App Store Connect API directly: it polls until the build finishes processing, creates the App Store version, fills in metadata and the "What's New" text, configures the App Clip's default experience (card subtitle, card image, all of it), and submits the version for review.

If you've ever configured an App Clip experience by hand, you know how many screens that involves. SWSH turned all of it into one more automated step.

SWSH manages App Store metadata through this custom pipeline instead of EAS Metadata, Expo's declarative metadata solution. Most teams don't need to build something like this and should just reach for EAS Metadata directly.

Validating a release before it ships

Before anything goes out, SWSH runs an in-house end-to-end testing framework called Meridian, built on Appium and WebDriverIO and driven by Vitest. Meridian exercises the flows that actually matter (login, sign-up, upload, deep linking) across iOS, the App Clip, and Android, on simulators and emulators, in CI. When SWSH marks a release as validated, that means the app was actually driven through those flows, not just that it compiled without errors.

SWSH validation gate and E2E Testing

Shipping JS-only fixes without a store review

For changes that don't touch native code, EAS Update lets SWSH push JavaScript fixes immediately, no store review required.

The part worth stealing: Meridian validates these too. An EAS Update ships new JavaScript onto a native build that's already installed on users' devices, so SWSH runs the same test suite against the updated bundle paired with the build it's landing on, to confirm the combination works and nothing regressed.

Watching for regressions after release

Shipping isn't where the release ends. SWSH uses EAS Observe to watch app performance in production, tracking runtime metrics like first render and time-to-interactive. When a release introduces a startup or rendering regression, it shows up there, and the team pushes a fix over the air instead of waiting for the next native release.

What this buys them

Engineers push changes and move on. The pipeline builds, validates, submits, and monitors each release on its own, across roughly 300 builds a month, a native release about once a week, and same-day JavaScript fixes shipped over the air.

SWSH's setup is a decent argument that shipping often and shipping safely aren't opposites. Get the pipeline right once, and the attention that used to go into release logistics goes back into the app itself.

Where to start

If you're eyeing something like this for your own app:

  • Start with EAS Build and EAS Submit in the cloud before you consider local builds. Most teams never need to self-host.
  • Add EAS Update for JS-only fixes once your native release cadence settles into a rhythm.
  • Look at EAS Metadata before building a custom App Store Connect integration like SWSH's. It covers most teams' needs without the extra maintenance.

This post is based on content from the Expo blog. Follow @expo for more React Native content.

Top comments (0)