When we started working on an Expo (React Native) application, we had a very simple goal. We just wanted the upload task to continue even if the app goes in the background or even if the app is killed. Sounds easy, right? But when you try to do it with Firebase Storage, things become a little messy.

In our case, the upload should be done directly to Firebase Storage. There is no REST API in between. Just a simple storage upload. We checked the Expo TaskManager library, we read about backgroundFetch, and we tried to understand if we can register our own custom function like uploadFile. But it was not that simple.

So in this blog, we will talk deeply about React Native Upload in Background, what works, what does not work, and what is the correct way to handle it in a clean and technical but simple manner.

First, What Exactly is the Problem?

Let me explain the real issue in very simple words. In an Expo React Native app, you want to upload a file (for example a video Blob) to Firebase Storage. The code looks something like this:

import { getStorage, ref, uploadBytes } from “firebase/storage”;

const storage = getStorage();
const storageRef = ref(storage, ‘videos’);

const uploadFile = async (file)=>{
// the file is Blob object
await uploadBytes(storageRef, file);
}

This works perfectly fine when the app is open and active. But the moment:

  • The app goes to background
  • The app is minimized
  • The app is killed or terminated

The upload stops. This is where React Native Upload in the Background becomes a serious topic.

We Tried Expo TaskManager… But Got Confused

Naturally, the first thing we checked was Expo TaskManager. We thought maybe we could register a custom task like uploadFile and let it run in the background. But then we found out something important:

  • Expo only allows some predefined background tasks.
  • You cannot just register any custom function.
  • backgroundFetch is designed for periodic fetch tasks.
  • It is not meant for long running file uploads.

Then on iOS, it asks to enable UIBackgroundModes. But the available options are:

  • audio
  • location
  • voip
  • external-accessory
  • bluetooth-central
  • bluetooth-peripheral
  • fetch
  • remote-notification
  • processing

There is no direct “upload” mode. So it gets confusing very fast. At this point, we realized that React Native Upload in Background with Firebase SDK is not straightforward at all.

Why Firebase UploadBytes is Not a Good Idea Here

After digging more, we found that uploadBytes and even uploadBytesResumable from Firebase JS SDK have issues in the Expo environment, especially in version 9. Many developers reported that it behaves unstable. Some times it breaks, sometimes it does not complete properly. So even if the background problem did not exist, it is still not the best option with Expo. That was the first big lesson.

So What is the Better Approach?

The better idea is actually very simple in concept. Instead of uploading directly to Firebase Storage from mobile app, you wrap the storage inside your own HTTP endpoint. This means:

  • Create a Firebase Cloud Function.
  • Let that function handle the upload.
  • From mthe obile app, send file using normal HTTP upload.
  • Now you can use any upload mechanism like:
  • FileSystem.uploadAsync
  • fetch
  • axios

Once the upload works through HTTP endpoint, then the background upload becomes much easier.
This is the clean architecture for React Native upload in background.

Use Signed URL

One approach is to create a small Firebase Function that generates a signed upload URL. Flow will look like this:

  • App calls /get-signed-upload-url
  • Server returns signed URL
  • The app uploads file directly to that signed URL

This works well in production. But there is one problem. It does not work nicely with the Firebase emulator. And if you are someone who likes to test a lot locally, this becomes painful. So many developers avoid this method for that reason.

Upload File Through Cloud Function (Better Control)

This method is a little more complex but more powerful. Instead of just giving a signed URL, you create an HTTP endpoint that accepts multipart form data. Inside that function:

  • Parse file using busboy
  • Store temporarily in tmp directory
  • Upload to Cloud Storage bucket
  • Optionally generate thumbnail
  • Return a successful response

This way, if the endpoint returns 201, you know the upload is fully done. No race conditions. No side effects. No messy background listeners. This design is very clean and professional

Basic Server-Side Idea

In Firebase Function, you can do something like:

  • Use busboy to read multipart data.
  • Save file to temp directory.
  • Upload to storage bucket using admin SDK.

Something like:

await bucket.upload(filepath, {
destination: `users/${userId}/${fileId}`,
contentType: mimeType,
});

Now storage is handled fully on the backend side. From a mobile app perspective, it is just a normal HTTP upload. And this is very important for React Native Upload in Background because background upload works much better with a standard HTTP request than the Firebase SDK upload.

Now Let’s Talk About the Mobile Side

On mobile side, you can use Expo FileSystem:

FileSystem.uploadAsync(uploadUrl, uri, {
httpMethod: “POST”,
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: “file”,
mimeType,
headers: {
“content-type”: “multipart/form-data”,
Authorization: `${idToken}`,
},
});

This works very nicely. It behaves like a normal file upload to the REST API.
And since it is not dependent on the Firebase JS SDK storage upload, it is more stable in Expo.

But What About the App Being Killed?

Now comes the hard truth. If the app is completely killed (terminated by the user), most JavaScript-based solutions will not continue uploading. Expo managed workflow has limitations. To achieve a true background upload even after termination:

  • You usually need native modules.
  • Or eject from Expo.
  • Or use platform-specific background transfer APIs.

For example:

  • On iOS → use URLSession background configuration.
  • On Android → use WorkManager or foreground service.

Libraries like:

  • react-native-background-upload
  • react-native-background-fetch

Often requires bare workflow (ejected Expo). So if your requirement is very strict, you may need to eject. There is no magic solution inside pure Expo managed workflow.

What about Firebase Cloud JSON API?

Another question that often comes up: Does the Firebase Cloud JSON API accept a file directly? Short answer: No, not directly like a normal file upload. You cannot just send a raw file easily using the JSON API without handling a proper multipart upload. That is why creating your own HTTP Cloud Function endpoint is cleaner. Then you control everything.

Why Wrapping Storage is the Smart Move

Let me explain this in a very simple way. If you treat cloud storage like a private internal system and expose only your own upload endpoint:

  • You gain full control.
  • You can validate file type.
  • You can limit file size.
  • You can attach authentication.
  • You can generate thumbnails.
  • You can log upload progress.

It becomes a more professional system.
And this architecture makes React Native Upload in Background more achievable and stable.

Real-world Perspective

In many real projects, especially in mobile + backend systems, we do not let the client talk directly to storage. We always wrap it. It is the same logic used in many Web Development Services projects also, where backend controls storage instead of exposing it directly. Mobile apps should not handle too much responsibility. Backend should manage heavy logic.

At The End

So let me summarize everything in simple words. If you are trying to achieve React Native Upload in Background in Expo app:

  • Do not rely fully on Firebase uploadBytes.
  • Do not expect TaskManager to run the custom upload function.
  • BackgroundFetch is not meant for long file uploads.
  • UIBackgroundModes does not provide a direct upload mode.
  • Firebase JSON API does not simply accept a file like that.
  • Instead:

  • Create your own Firebase Cloud Function endpoint.
  • Accept multipart form data.
  • Use busboy to parse the file.
  • Upload file to Cloud Storage using admin SDK.
  • From the mobile app, use FileSystem.uploadAsync.

If you need a true background upload even when the app is killed, then you may need to eject Expo and use native background transfer APIs. There is no perfect shortcut here. But once you understand the architecture, things become much more clear. And honestly, once you wrap storage inside an HTTP endpoint, your system becomes more scalable, more secure, and more maintainable. That is the real professional way to handle React Native Upload in Background without fighting the limitations of Expo and Firebase SDK.