Playback works. Uploading throws about a native module that does not exist. Both screens are in the same app, both imports look identical, and the difference isn't in your code at all. Expo Go is a prebuilt app with a fixed set of native modules compiled into it. The FastPix Uploads SDK isn't one of them. No amount of clearing the Metro cache changes that, because the missing piece is compiled native code rather than JavaScript.
TL;DR
Expo Go is a published app with a fixed set of native modules compiled in, and you can't add yours to somebody else's binary. Playback works there because expo-video is part of the Expo SDK. The FastPix Uploads SDK isn't, so the upload screen throws about a missing native module. The fix is a development build: npx expo run:ios or npx expo run:android, or EAS Build if you would rather not install native tooling. It isn't ejecting. You keep npx expo start, fast refresh and over-the-air JavaScript updates.
Why Expo Go cannot load the Uploads SDK
Expo Go isn't a runtime that loads your project. It's a published app on the App Store and Play Store, built by Expo. It contains a specific set of native modules, chosen in advance.
When you scan a QR code, Expo Go downloads your JavaScript bundle and runs it against the native modules it already has. Your JavaScript is yours. The native layer isn't, and it's fixed at the version Expo shipped.
So a library with no native code works instantly, and a library with native code can't work at all.
| What the library contains | Runs in Expo Go |
|---|---|
| JavaScript only | Yes |
| Native module already bundled into Expo Go, such as expo-video | Yes |
| Native module not bundled, such as the FastPix Uploads SDK | No |
What you need before you start
- An Expo app, and roughly ten minutes for a first native build.
- Xcode for iOS, Android Studio for Android, or an EAS Build account to avoid both.
- A FastPix account with an Access Token ID and Secret Key from activate your account.
Why Expo Go plays video but cannot upload it
The quickstart player is built on expo-video, which is part of the Expo SDK and therefore already inside Expo Go. That's why a video plays on a phone within minutes of starting.
The Uploads SDK is a different kind of package. Chunked, resumable upload needs file-system access, background transfer behaviour and platform networking. JavaScript can't reach any of that on its own, so the library ships native code. That code has to be compiled into the app binary, and Expo Go's binary was compiled long before your project existed.
Picture-in-Picture fails in Expo Go for the same reason, even though expo-video itself is present. The feature needs native configuration that the generic Expo Go binary doesn't carry.
An Expo development build is not ejecting
The fix is a development build. It's worth being clear about what that is, because people avoid it expecting to lose the Expo workflow.
A development build is your own version of Expo Go: the same fast-refresh, QR-code, JavaScript-over-the-wire experience, compiled with your project's native modules included. You keep npx expo start. You keep over-the-air JavaScript updates. What changes is that the shell running your bundle is yours.
npx expo run:ios
# or
npx expo run:androidEither command prebuilds the native project, compiles it, and installs the result on your simulator or device. From that point, npx expo start behaves exactly as it did, and the upload screen works.
You can try this against a real stream before committing to it. The free plan covers 10 videos and 100K streaming minutes a month with no credit card.
If you would rather not install Xcode or Android Studio, EAS Build compiles it remotely and hands back an installable build. That's the usual route for teams where not everyone has native tooling set up.
Rebuild the native app, do not reload the bundle
This is the step that wastes the most time, because reloading looks like it should be enough.
Adding a native module changes the binary. Reloading the JavaScript bundle doesn't rebuild the binary. The app on your device is still the old one, and the module is still missing. The same applies after pod install on a bare project: pods change the native project, and the native project has to be compiled again.
So after installing anything with native code, run npx expo run:ios or npx expo run:android again, or rebuild from Xcode. A cache clear with npx expo start -c is for a stale JavaScript bundle, which is a different problem entirely.
The Expo video upload screen, once the build exists
With a development build installed, the upload flow is ordinary React Native code. The SDK takes a function rather than a URL. A signed URL is minted when the user actually picks a file:
import { FastPixUpload } from "@fastpix/react-native-uploads";
import * as ImagePicker from "expo-image-picker";
const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: "videos" });
if (result.canceled) return;
const upload = new FastPixUpload({
endpoint: createUploadUrl, // async function hitting your backend
fileUri: result.assets[0].uri, // pass the file:// URI through unchanged
chunkSize: 5 * 1024, // KB, divisible by 256, minimum 5120
});
upload.on("progress", ({ percentage }) => setPercentage(percentage));
upload.on("success", () => setStatus("success"));
await upload.start();expo-image-picker returns a file:// URI. Pass it through unchanged rather than stripping it.
chunkSize is in KB, must divide by 256, and must be at least 5120. The SDK checks both in the constructor and throws before any bytes are sent, so a bad value is an immediate error rather than a late HTTP 400.
The backend creating the URL must omit the X-Client-Type: web-browser header. A native uploader PUTs chunks directly rather than performing the browser handshake, so sending it fails every chunk with HTTP 400.
The full upload screen, including pause and resume, is in resumable video upload in React Native. Where that backend call goes for each framework is covered in where the upload URL is created in every framework.
Keep Expo Go for playback work
A development build doesn't have to replace Expo Go in your workflow. Playback still runs there, and the QR-code loop is genuinely faster for UI work.
Teams tend to settle on the same split. Screens that only render and play video get iterated in Expo Go. The upload screen, and anything else touching native modules, gets tested on the development build. Both run the same JavaScript bundle, so you aren't maintaining two versions of anything.
Build once and get uploads running
Run npx expo run:ios or npx expo run:android, install the result, then go back to npx expo start exactly as before. The upload screen works from that point, and your development loop is unchanged. The free plan covers 10 videos and 100K streaming minutes a month, no credit card. Test a real chunked upload from a real device as soon as the build lands.
Frequently Asked Questions (FAQs)
Why does upload fail in Expo Go but playback works?
Because Expo Go is a prebuilt app with a fixed set of native modules. The FastPix Uploads SDK ships native code that is not among them. The quickstart player is built on expo-video, which is part of the Expo SDK and already inside Expo Go. Chunked resumable upload needs file-system and networking behavior that JavaScript cannot reach alone.
What is the "native module does not exist" error in Expo?
It means the JavaScript side of a library is present, but the compiled native side is not in the running app. In Expo Go, this is permanent because you cannot add native modules to someone else's binary. Create a development build with npx expo run:ios or npx expo run:android and the module is compiled into an app you own.
Is a development build the same as ejecting?
No. A development build is your own version of Expo Go, compiled with your native modules included. You keep npx expo start, fast refresh, QR codes, and over-the-air JavaScript updates. Ejecting was about abandoning the managed workflow, and a development build does not do that.
Do I need Xcode or Android Studio for a development build?
Not if you use EAS Build. It compiles remotely and returns an installable build. Local builds with npx expo run:ios or npx expo run:android do need the platform tooling. Teams often use EAS so that members without native tooling can still install and test.
I installed the package and it still says the module is missing.
Reloading the JavaScript bundle does not rebuild the binary. A native module lives in the binary. Run npx expo run:ios or npx expo run:android again after installing anything with native code. On a bare project, pod install also needs a rebuild afterward, since installing pods alone does not recompile an app already on the device.






