Expo Local iOS Build on Windows: Build an Expo ipa Without EAS
Last updated
An Expo local iOS build on Windows works without EAS Build. Fill in the iOS fields in app.json, run doctor to generate the ios folder and run pod install, start Metro and press F5 to put a development build on your iPhone. After app.json edits, delete the ios folder and regenerate it. A Release build then gives you an .ipa for App Store Connect, with no queue and no cloud build quota. You need an Apple Developer account, an iPhone, a USB cable and KXBuild installed. The first run takes about an hour.
Should you build locally or with EAS Build?
EAS Build builds on Expo’s cloud macOS machines, so you don’t maintain a build environment, and EAS can manage your certificates too. The trade-off is that free accounts get a limited number of builds per month, builds queue at peak times, and every change to native configuration means waiting for another cloud build. eas build --local builds on your own machine, but for iOS it still requires macOS and Xcode.
Building locally on Windows with KXBuild means no queue, no cloud build quota, and breakpoints in native code. You provide your own certificates and provisioning profiles, and the ios folder is generated on your machine. The two approaches can coexist. Some people on a team can ship production builds with EAS while others debug locally on Windows.
How do you create an Expo project?
To use an existing project, open its root folder in VS Code with File > Open Folder… and skip to the next section.
Otherwise, create one with Expo’s official command. Use a short path for the parent folder. node_modules nests deeply, and a deep project path easily runs into path-too-long errors on Windows.
1cd D:\work
2npx create-expo-app@latest expohello
When it’s done, open D:\work\expohello in VS Code.
You can also create the project from a KXBuild template. Use either of these.
- VS Code extension. Run
KXBuild: New Projectfrom the Command Palette and pick the “Expo App” template. - Command line. Run
kxbuild project create -t expo_app -n expohello -o D:\work.
For details on the template, see Project templates.
Which iOS settings go into app.json first?
An Expo project’s ios folder is generated from app.json, so filling in the iOS fields before generating it saves you a regeneration. Only the iOS-related fields are shown below. Leave the others as they are.
1{
2 "expo": {
3 "name": "expohello",
4 "version": "1.0.0",
5 "ios": {
6 "bundleIdentifier": "com.example.expohello",
7 "buildNumber": "1",
8 "appleTeamId": "ABCDE12345",
9 "infoPlist": {
10 "NSCameraUsageDescription": "Used to scan QR codes",
11 "NSMicrophoneUsageDescription": "Used to record audio in videos"
12 }
13 }
14 }
15}
Here’s what each field does.
| Field | Purpose |
|---|---|
version |
The version shown on the App Store, maps to CFBundleShortVersionString |
ios.buildNumber |
The build number, maps to CFBundleVersion. Each upload to App Store Connect needs a higher one than the last |
ios.bundleIdentifier |
The bundle ID. It must match the App ID registered in your developer account and the provisioning profile |
ios.appleTeamId |
Your developer account’s Team ID, shown on the Membership page at developer.apple.com |
ios.infoPlist |
Keys written straight into Info.plist. Put usage descriptions here |
Apps that use the camera, microphone, photo library or location must include the matching usage description. Without it, the app crashes as soon as it makes the call, and App Review rejects it. Some Expo libraries, such as expo-camera, let you set the permission text under plugins. Follow that library’s docs; the result is the same.
Put the .p12 certificate and .mobileprovision provisioning profile for signing in the keychain folder. During development, use an Apple Development certificate and a Development provisioning profile that includes this iPhone’s UDID. For how to get and place them, see iOS code signing on Windows.
How do you generate the ios folder and install native dependencies?
Run doctor first whenever you open a project for the first time. Doctor checks your Expo setup, then generates and prepares the iOS project. Afterward the project has an ios folder. Use either of these.
- VS Code extension. In the title bar of the KXBuild view, open the ··· menu and run Doctor (fix environment).
- Command line. Run the command below.
1kxbuild doctor D:\work\expohello
After doctor, go into the ios folder and install the native dependencies. The first time downloads quite a lot, so how long it takes depends on your network.
1cd D:\work\expohello\ios
2pod install
Afterward the ios folder contains expohello.xcworkspace. Build that from now on. For more on CocoaPods, see Develop iOS apps with CocoaPods on Windows.
How do you run a development build on your iPhone?
Connect the iPhone with a cable, unlock it and tap Trust in the Trust This Computer prompt, then turn on Developer Mode in Settings > Privacy & Security > Developer Mode. The iPhone restarts once. For drivers and iPhone settings, see Set up your device.
A development build loads its JS from Metro on your computer, so first open another terminal and start Metro in the project root.
1cd D:\work\expohello
2npx expo start
Then select the iPhone in the KXBuild view and press F5. KXBuild builds, signs, installs and launches the app, then attaches the debugger. The first time you debug on a given iOS version, KXBuild downloads its device symbols. It’s a few GB, but only once.
Once the app is running, save changes to JS or TypeScript code and the app refreshes automatically, with no rebuild. Each kind of code uses its own debugger.
- Native code (Swift and Objective-C in the ios folder). Set breakpoints in VS Code and they take effect after F5. See Run and debug.
- JS code. Press
jin the terminal runningnpx expo startto open Expo’s debugger and set breakpoints there.
If the app shows a red screen as soon as it opens, the iPhone usually can’t reach Metro on your computer. Make sure the iPhone and computer are on the same local network and that Windows Firewall allows Node.js or Metro’s port 8081.
After changing native code, stop debugging before pressing F5 again. The Restart button on the debug toolbar doesn’t recompile.
Why regenerate the iOS project after editing app.json?
If you change the bundle ID, version, usage descriptions or icon, or install a library with native code, regenerate the ios folder, or the change won’t reach the app. Doctor only generates the ios folder when it doesn’t exist, so delete the old one first, then run doctor.
1cd D:\work\expohello
2Remove-Item -Recurse -Force .\ios
3kxbuild doctor D:\work\expohello
4cd ios
5pod install
In the extension, delete the ios folder, run Doctor (fix environment) in the KXBuild view, then run pod install in the ios folder. The result is the same.
Regenerating overwrites every manual change in the ios folder. Keep native configuration you want to last in app.json or a config plugin, which is what Expo recommends. If your project keeps the ios folder in version control and edits native files directly, don’t delete the folder. Edit it with the Property List Editor and Project Settings Editor instead.
How do you build an Expo .ipa locally?
Check two things before submitting. ios.buildNumber must be higher than in your last upload, and if you changed it, regenerate as in the previous section. The keychain folder must have an Apple Distribution certificate and an App Store provisioning profile, since a build signed with a Development profile is rejected on upload.
A Release build bundles the JS into the app, so it no longer depends on Metro at runtime. Use either of these.
- VS Code extension. Run
KXBuild: Build for App Store (.ipa)from the Command Palette or the project’s right-click menu. - Command line. Run the command below in the project root.
1kxbuild build .\ios\expohello.xcworkspace -c Release --export app-store
--export app-store signs with the Apple Distribution certificate and App Store provisioning profile in the keychain folder, whatever certificate the project is set to use. If it reports that no App Store profile was found, put the App Store .mobileprovision in keychain, along with the Distribution .p12 and its password in password.properties.
The build produces ios\build\expohello.ipa, ready to upload. Upload it to App Store Connect with Appuploader on Windows. When processing finishes, it appears on the TestFlight page. Send it to testers first, then submit it for review. See Release and App Store submission.
Teammates who ship with EAS aren’t affected. An Expo local iOS build and EAS Build read the same app.json, and each side generates its own ios folder.
FAQ
What if doctor didn’t generate the ios folder?
Run it again with -v, as in kxbuild doctor D:\work\expohello -v. The output names the step that failed, which is usually dependencies that didn’t finish installing or a network interruption.
What if the build can’t find a header or module from Pods?
Make sure you ran pod install in the ios folder and that you’re building the .xcworkspace, not the .xcodeproj.
What if you get “file name too long” or “file not found”?
Move the project to a short path such as D:\work, delete the ios folder and regenerate it.
Why does the app quit right after launch with “didn’t start in time”?
The app most likely crashes on launch, usually because a usage description is missing or native libraries weren’t regenerated. To view device logs and crash reports, see Run and debug and Troubleshooting.