AppForShare

eas build --local: share the .ipa and .apk with testers

· 7 min read

eas build --local: share the .ipa and .apk with testers

eas build --local runs the same build steps as Expo's cloud workers, on your own computer. People use it to skip the free queue, to keep builds off a monthly count, or to debug a failing build step by step. The catch shows up at the end: you get a file on your disk, and no build page on expo.dev to share. This guide shows how to make a local build that testers can actually install, and how to get it to them.

What a local build is, and what it leaves out

Expo's local builds page (read on November 4, 2026) lists the main differences from a cloud build (Expo local builds docs):

  • There is no caching, so builds take longer.
  • Secret environment variables stored on Expo are not available. Set them in your own shell.
  • It builds one platform at a time. Run the command once for iOS and once for Android.
  • iOS needs a Mac with Xcode. Android needs the Android SDK and a Java runtime.

The result is a plain file. Because the build never ran on Expo's servers, there is no internal distribution link for it. An old Expo forum thread titled "How to distribute internally with EAS --local build for iOS" asks exactly this question, and it is still a common one.

Why teams build locally

As listed on November 4, 2026 at expo.dev/pricing, the Free plan includes 15 Android and 15 iOS builds a month in a low priority queue with a 45 minute timeout. Starter is $19 a month plus usage, with $45 of build credit. Expo's status page recorded "Increased EAS jobs wait times for free tier users" on May 5, 2026. If you have a Mac on your desk, a local build skips both the queue and the count. Building on your own GitHub Actions minutes is another way around the queue, covered at the end of this guide.

Step 1: a profile testers can install

The profile decides what kind of file you get. For testers you want internal distribution:

{
  "build": {
    "preview": {
      "distribution": "internal",
      "channel": "preview",
      "android": { "buildType": "apk" },
      "ios": { "simulator": false }
    }
  }
}
  • iOS: internal distribution signs the build ad hoc (or enterprise, if your account has it). An ad hoc build installs only on iPhones listed in its provisioning profile. A simulator build does not install on a phone at all.
  • Android: buildType: apk gives a file a phone can install. Without it you may get an .aab. That is fine too: AppForShare turns an uploaded .aab into one universal APK (.aab uploads).
  • channel: if you use EAS Update, keep test builds off the production channel. More in EAS Update channels and test builds.

Step 2: run the build and pick the output path

eas build --platform ios --profile preview --local --output ./dist/app-preview.ipa
eas build --platform android --profile preview --local --output ./dist/app-preview.apk

Without --output, the file lands in the project folder with a generated name. Giving it a fixed path keeps the next step simple. Add dist/ to .gitignore so builds never end up in the repository.

Step 3: check the file before anyone installs it

Most "unable to install" messages on iPhone come from a profile that does not list the tester's phone, or from the wrong export type. The free IPA inspector reads the .ipa in your browser without uploading it and shows the distribution type, the profile expiry and every UDID in the profile. Check that the new tester's phone is in that list before you send the link.

Step 4: share it with one command

You have a few ways to get the file to people:

WayWorks forLimits
Send the file by chat or emailAndroid testersAn .ipa sent as a file does not install on an iPhone by tapping it
Expo OrbitYour own devices and simulatorsA desktop app for the developer, not for outside testers
Upload in the browserAnyone with a linkFree plan: 150 MB files, links last 7 days
AppForShare CLIAnyone with a link, from a scriptPro and Team, upload token

With the CLI, one line uploads both files and puts them into the Beta channel:

export APPFORSHARE_TOKEN=your_upload_token
npx appforshare upload ./dist --channel beta --notes "Login screen fixes"

Given a folder, upload sends the newest build in it. Run it once per file if you prefer to name them. You can also run npx appforshare init once, which writes a small config with your build folders, and then plain npx appforshare upload sends the newest .ipa and the newest .apk in one run. Use --ios-notes and --android-notes when the two builds have different notes.

The Beta channel link always opens the newest build put into it: iPhone users get the iOS build, Android users the Android build (Beta and Live channels, Pro). Testers keep the same link for every round.

Put it in one script

"scripts": {
  "share:ios": "eas build -p ios --profile preview --local --output dist/app-preview.ipa && npx appforshare upload dist/app-preview.ipa --channel beta",
  "share:android": "eas build -p android --profile preview --local --output dist/app-preview.apk && npx appforshare upload dist/app-preview.apk --channel beta"
}

EAS also runs npm hooks such as eas-build-on-success inside the build (npm hooks docs), but the path of the finished file is not documented there, so running the upload after the build command is the safer choice.

New iPhones mean a new build

Every new tester iPhone has to be in the profile, and the profile only changes when you build again. Two ways to handle it:

  • With EAS: eas device:create registers the phone with Expo. Expo's docs say it reaches Apple only when the next build includes it, so build again after adding phones.
  • With AppForShare: send the iPhone onboarding link (Pro and Team). The phone lands in your devices list, you register it with Apple using your App Store Connect API key, and the tester gets one email when a build that includes the phone is uploaded (iOS device registration). Then run eas device:create and pick the Apple Developer Portal import so EAS sees the same phones.

Going the other way, npx appforshare devices import reads the output of eas device:list --json, so phones you already have on EAS show up in AppForShare too.

When a local build is not the right tool

Local builds tie the build to one person's machine. If several people ship test builds, a shared build is easier to repeat. You can keep the cloud build on EAS and have each finished internal build land in AppForShare through Connect Expo (Connect Expo, Pro and Team). Or, on Pro and Team, AppForShare can set up builds on your own GitHub Actions minutes, with an Expo preset that runs npx expo prebuild before the build (GitHub Actions builds). EAS is ahead in one clear way here: its cloud builds iOS without you owning a Mac.

More for EAS teams: how EAS internal distribution links work, registering testers' iPhones with eas device:create and what to do when an EAS build waits in the queue.

Common questions

Does a local build show up on expo.dev?

No. It runs on your machine and leaves the file there. That is why it has no internal distribution link.

Can testers install the .ipa from a local build?

Yes, if it is signed ad hoc and their iPhone is in the profile, or signed enterprise. Upload it and send the install link. App Store signed builds do not install from a link.

Does AppForShare build the app for me?

No. AppForShare does not build apps on its own machines. You build locally, on EAS or on your own GitHub Actions, and AppForShare shares the result.

Is there more on using both?

Yes: Use AppForShare with Expo EAS and the AppForShare vs Expo EAS comparison. If you are moving clients off Expo Go, see Expo Go now needs a login.

Your local build is only useful once it is on a tester's phone. Create a free account and upload the file from dist/ to get your first install link.

Keep reading

App Distribution Platform · · 5 min read

AppForShare File Size and Upload Limits

AppForShare limits the size of each build you upload by plan: 50 MB without an account, 150 MB on the Free plan, 2 GB on Pro and 4 GB on Team. Accounts also have a storage total, and every plan...