File upload looks simple until it reaches production. In a React Native app, the hard parts are rarely the button or picker. The real work is choosing where files should live, how uploads are authorized, what happens on weak networks, how public URLs are exposed, and how media costs and SDK constraints affect your stack over time. This guide gives you a practical way to think about React Native file upload using Firebase Storage, Supabase Storage, Amazon S3, and Cloudinary. It is designed to help you choose a sensible default, implement it with fewer surprises, and know when to revisit the decision as your app, team, or infrastructure changes.
Overview
If you are evaluating react native file upload options, the best choice usually depends on one question: are you buying storage only, or a broader application platform? Firebase, Supabase, S3, and Cloudinary all support a workable upload flow, but they solve different surrounding problems.
A useful mental model is this:
- Firebase Storage fits teams already using Firebase for auth, database, analytics, or push messaging. It can be a natural extension of an existing backend stack.
- Supabase Storage fits teams that want a Postgres-centered platform, SQL-friendly workflows, and a tighter connection between storage policies, auth, and database records.
- Amazon S3 fits teams that want low-level control, broader cloud flexibility, and a vendor-neutral storage layer that can sit behind their own APIs.
- Cloudinary fits apps where media transformation is part of the product, such as image resizing, optimization, format conversion, or delivery variants for different screens.
For many apps, uploads are not only about storing a blob. You also need metadata, ownership, validation, moderation, lifecycle rules, thumbnails, and a way to display files reliably in the UI. That is why storage selection belongs in an integrations guide, not just a code snippet collection.
Before you compare providers, define the kind of file workflow you actually have:
- User avatar uploads
- Product gallery images for ecommerce
- PDF and document attachments
- Short-form video or audio
- Offline-first uploads that sync later
- Admin-managed assets for content apps
Those use cases have different expectations around file size, retry behavior, transformations, access control, and delivery speed. A profile photo flow can tolerate simple direct uploads. A marketplace with many seller images may benefit from transformation pipelines. A regulated document workflow may require server-mediated upload signing and stricter path design.
If you are still picking your broader stack, it helps to read this alongside Expo vs Bare React Native: Which Stack Should You Choose for Your Next App? because your upload implementation may differ depending on native module needs, background behavior, and how much custom platform code you want to maintain.
Core framework
The most reliable way to implement file uploads is to separate the problem into five layers: file selection, upload transport, storage destination, metadata persistence, and file delivery. If you keep these layers distinct, switching from firebase storage react native to supabase storage react native or s3 upload react native becomes much easier later.
1. File selection
Your app first needs a predictable source file object. In React Native, that often comes from an image picker, document picker, or camera flow. At this stage, normalize what you can:
- URI or local path
- MIME type
- File name
- Size in bytes
- Image dimensions if relevant
Do not assume every picker returns the same shape on iOS and Android. Normalize early and pass a stable object into your upload layer.
2. Upload transport
This is how bytes move from device to remote storage. Some providers support direct client uploads. Others work best through signed URLs or your own backend endpoint. The right choice depends on trust boundaries.
As a general rule:
- Use direct client upload when SDK support is strong and your access rules are simple.
- Use signed upload URLs when you want tighter control without proxying the whole file through your backend.
- Use server-mediated uploads when you need inspection, virus scanning, strict validation, or custom business logic before storage.
For many production apps, signed uploads are the most balanced pattern. They reduce backend bandwidth while keeping credentials and authorization logic off the client.
3. Storage destination
This is where your provider differences matter most:
- Firebase Storage is usually easiest when your app already trusts Firebase auth and security rules.
- Supabase Storage is attractive when row-level permissions and SQL-backed metadata are central to your app.
- S3 is a strong default when you want infrastructure flexibility or already run backend services in AWS.
- Cloudinary works well when upload and delivery should feed image or video transformation workflows.
A common mistake is treating all four as interchangeable object stores. They are not. Storage is only one part of the developer experience. The surrounding auth model, SDK quality, dashboard ergonomics, and delivery features often matter more than the raw upload API.
4. Metadata persistence
Never rely on storage alone as your system of record. Save a metadata record in your database with fields such as:
- Owner or account ID
- Storage key or public ID
- Original filename
- Content type
- File size
- Status: pending, uploaded, failed, processing
- Created at and updated at
- Optional width, height, duration, or checksum
This makes UI state, cleanup jobs, and migration work much easier. If you are pairing uploads with app data, this also connects well with a broader persistence strategy such as the patterns discussed in React Native Database Guide: SQLite vs Realm vs WatermelonDB vs AsyncStorage.
5. File delivery
Uploading is only half the lifecycle. Decide how files are read back:
- Public URL
- Signed temporary URL
- Authenticated API route
- Transformed CDN URL
For profile images or public catalog media, public delivery may be enough. For invoices, private documents, or user-generated sensitive content, temporary access URLs or authenticated proxies are usually safer.
A practical provider selection lens
Use these decision points when comparing providers:
- Existing stack fit: Are you already committed to Firebase, Supabase, AWS, or a media platform?
- Expo compatibility: Do you need a flow that stays friendly to your current Expo setup?
- Transformation needs: Do you need thumbnails, cropping, compression, or video handling built in?
- Authorization model: Can the client upload directly, or must the server issue permission per file?
- Operational control: Do you want a managed platform or lower-level building blocks?
- Migration risk: How difficult would it be to move your file keys and URLs later?
If your app already depends on Firebase auth, database, and messaging, Firebase Storage may keep your architecture simple. If your team prefers Postgres-backed tooling and wants storage closely tied to database policies, Supabase is often easier to reason about. If your backend team values cloud primitives and custom workflows, S3 is usually the most adaptable. If media transformation is central to product quality, Cloudinary can remove a lot of custom image pipeline work.
Practical examples
Below are concrete implementation patterns rather than provider-specific code listings. The goal is to show how the architecture changes by use case.
Example 1: User avatar upload
This is the simplest common flow. The user selects a photo, your app uploads it, and the profile record stores a stable reference.
Recommended pattern:
- Pick image from camera or library.
- Validate size and MIME type on the client.
- Upload to a path such as
users/{userId}/avatar/currentor a versioned equivalent. - Store the resulting file key or URL in the user profile table.
- Render from a cached remote URL in the app.
This works well with Firebase Storage or Supabase Storage if your auth and storage rules are already defined. Cloudinary may be especially attractive if you want automatic size variants for profile lists, chat headers, and account screens.
Example 2: Product gallery images for an ecommerce app
This flow benefits from explicit metadata and consistent naming. A seller may upload multiple images per product, reorder them, replace them, or mark one as primary.
Recommended pattern:
- Create a database record for the image first with status
pending. - Generate a stable key like
products/{productId}/{imageId}.jpg. - Upload using a signed URL or direct SDK method.
- Mark the record
uploadedafter success. - Store image dimensions and display order in the database.
If you also need responsive delivery and transformation, Cloudinary can reduce frontend complexity. If your store already uses AWS services, S3 plus your own image processing layer may be more consistent with the rest of your backend. For teams building from a template, this kind of asset workflow often appears in starter projects, so it is worth reviewing Best React Native Starter Kits and Boilerplates for SaaS, Ecommerce, and Content Apps.
Example 3: Private document upload
For resumes, contracts, receipts, or internal reports, avoid public URLs by default.
Recommended pattern:
- User requests upload permission for a document type.
- Your backend validates the action and returns a signed upload target.
- The app uploads directly to storage.
- Your backend stores metadata and later issues short-lived download access.
This pattern is often cleanest with S3 because signed upload and download workflows are common in custom backend architectures. It can also be implemented on other platforms, but the key principle remains: sensitive files should not be casually exposed through permanent public links.
Example 4: Content app with many editorial images
If your team publishes banners, illustrations, or article media often, transformation and delivery matter as much as upload. You may need resized thumbnails, compressed mobile versions, and high-quality originals.
Recommended pattern:
- Upload the original file once.
- Keep a canonical source reference in your CMS or database.
- Use transformed delivery URLs for different app surfaces.
- Cache aggressively in the app where appropriate.
This is where a cloudinary react native workflow can be appealing because the delivery layer does more of the heavy lifting.
Example 5: Uploads combined with auth, analytics, and notifications
Many apps need uploads to trigger downstream events: notify a reviewer, update a feed, or track completion. In that case, avoid embedding business state only in the storage path. Write an application record and let other systems react to that record.
For example:
- Auth verifies who owns the upload.
- Storage receives the file.
- Database stores metadata and state.
- Notifications fire when processing completes.
This broader system design fits naturally with integrated stacks. Related decisions often overlap with auth and push choices, so you may also want to compare Best React Native Authentication Solutions: Clerk, Firebase Auth, Auth0, Supabase, and More and React Native Push Notification Services: OneSignal vs Firebase vs Expo Notifications.
Common mistakes
Most upload bugs in production are architectural, not visual. These are the issues worth catching early.
Storing only a URL and losing the file identity
Always save the provider-specific file key, ID, or path in addition to any rendered URL. URLs may change. A stable storage reference makes cleanup, migration, and re-signing possible.
Hard-coding provider logic into UI components
Your screen component should not know whether a file ends up in Firebase, Supabase, S3, or Cloudinary. Wrap upload behavior in a service interface such as uploadFile(file, context) and keep provider details below that layer.
Skipping progress, retry, and cancel states
Mobile uploads fail more often than desktop uploads. Networks change, the app backgrounds, and users switch screens. Even a simple progress state and retry path can prevent many support issues.
Using public buckets for private content
Convenience at launch often creates exposure later. If a file is not meant for the public internet, design for signed or authenticated access from day one.
Ignoring image size before upload
Uploading full-resolution photos when your app only needs a modest display size wastes bandwidth and slows the UI. Consider preprocessing, compression, or provider-side transformation depending on your stack.
Forgetting cleanup rules
What happens when a user replaces an avatar, deletes a product, or abandons a draft upload? Without cleanup, storage accumulates orphaned files and confusing metadata.
Choosing a provider based only on the first tutorial you find
The easiest getting-started guide may not match your medium-term architecture. Think in terms of auth model, metadata, delivery, and migration path, not only upload success on a demo screen.
Not testing Expo and native edge cases early
Some upload paths behave differently depending on your runtime, picker choice, and native dependencies. Validate your plan early if you are targeting Expo-managed workflows or expect to use background-heavy behaviors. This is one reason expo tools decisions and upload decisions should be evaluated together.
When to revisit
Your first storage choice does not need to be permanent, but it should be deliberate. Revisit your upload architecture when one of these conditions appears:
- Your file types change. Moving from images to video or large documents often changes provider fit.
- Your security model tightens. Public file access that felt fine in an MVP may not suit production.
- Your stack consolidates. If your team standardizes on Firebase, Supabase, or AWS, reducing tool sprawl may matter more than feature differences.
- You add image or video transformation requirements. Delivery features can become more important than storage itself.
- You need better observability. Upload failures, processing delays, and media delivery issues should be traceable in your monitoring stack.
- You are preparing for scale or migration. Stable file keys, metadata records, and an abstraction layer make migrations much less painful.
A good action plan is simple:
- Write down your primary upload use cases and their privacy requirements.
- Choose a provider that fits your current backend direction, not just your current screen.
- Abstract the upload API in your app so storage can change later.
- Persist metadata outside the storage layer.
- Test weak-network behavior, retries, and replacement flows before shipping.
- Schedule a review when your stack, media needs, or security expectations change.
For teams treating React Native as a long-lived product platform, file upload should sit alongside other stack choices such as payments, CI/CD, and data layers. If you are formalizing that wider architecture, related guides on React Native Payment Integrations and React Native CI/CD Tools Compared can help you keep infrastructure decisions consistent.
The durable takeaway is this: pick the provider whose surrounding system matches your app. Firebase Storage, Supabase Storage, S3, and Cloudinary can all support React Native well, but the best long-term choice is the one that keeps upload flows understandable, secure, and easy to evolve.