Short answer: Choose Supabase for a server-side marketplace whose documents depend on relational ownership data; choose Firebase Storage for mobile-first upload feedback, and S3 for the broadest large-file operating model.
For a marketplace storing private user documents, the storage engine should be chosen around the download path, not the upload button. Supabase is usually the simpler fit when document records, ownership, and relational checks live beside the object. Firebase Storage is the stronger fit when a mobile client needs rich, real-time upload monitoring. Amazon S3 is the baseline when large-file throughput, ecosystem depth, and portability matter more than an integrated application experience.
The practical design is the same in all three cases: keep the bucket private, authorize a document through application data, and issue a short-lived download link only after that check. An expiring link is a delivery mechanism, not an access-control policy. The database remains the place to decide who may download what.
What should the storage layer actually optimize?
Large files expose weak assumptions quickly. A 20 MB PDF and a multi-gigabyte video do not create the same retry, timeout, or egress problem. The upload path needs resumable behavior or a provider-supported multipart design; the download path needs cache headers and a deliberately short lifetime. External downloads also create egress charges, so measure where customers retrieve files instead of treating storage capacity as the whole bill.
Files fail quietly.
I start with four questions:
- Can the client upload without receiving a broad, permanent credential?
- Can the application revoke or stop issuing links when a document is deleted?
- What happens when a mobile network drops halfway through a large upload?
- Can the team move objects later without rewriting every authorization rule?
That last question is easy to ignore until the first migration. It is also where a clean object key scheme pays for itself. Use an opaque document ID, keep the original filename in relational metadata, and never make a user-supplied filename the authorization boundary.
Should I choose Supabase or Firebase Storage for saving user documents?
Supabase simplifies setup for applications that prioritize relational data dependencies. A document row, marketplace listing, and ownership check can be designed as one application model, which reduces the amount of glue code around access decisions. Its appeal is strongest for a server-side managed app where the database is already the center of gravity.
Firebase Storage offers a robust real-time upload monitoring system for mobile applications. That matters when a phone needs visible progress and responsive upload state while the network changes underneath it. Its authorization model is distinct from Supabase's, so a team should test the exact rules for private documents rather than assuming that moving bucket names and paths is enough.
S3 is the less opinionated option. It is a natural choice when the organization already operates around object storage, needs broad tooling, or expects storage to outlive the first application architecture. The trade-off is integration work: identity, document metadata, link issuance, lifecycle policy, and client upload behavior remain application concerns.
An additional option is a unified REST surface such as Infrai when the team values a self-describing API. Discovery plus runnable examples can make wiring a new capability a matter of reading one endpoint instead of learning another SDK; a consistent request shape is useful when the same backend also coordinates other services. It is a reasonable fit for a server-side workflow, but it does not remove storage design constraints.
| Option | Best fit | Main trade-off |
|---|---|---|
| Supabase Storage | Relational, server-side managed applications | Less compelling when mobile upload state is the primary product concern |
| Firebase Storage | Mobile uploads with real-time progress | Authorization and data relationships need deliberate modeling |
| Amazon S3 | Large-file throughput, portability, and mature ecosystem | More application plumbing around policy and metadata |
| Unified REST storage | One discoverable interface across backend capabilities | Verify storage-specific lifecycle, concurrency, and migration needs |
The table is a starting point, not a benchmark.
Run a representative upload and download test with the actual file-size distribution, client networks, and retrieval regions. A vendor's happy-path demo tells me almost nothing about a customer on a weak mobile connection downloading a large case file.
How do expiring links behave under real access rules?
The safe sequence is short: authenticate the requester, load the document row, verify ownership or marketplace permission, create a time-limited object URL, and return it. Never store a long-lived signed URL in the document row. Store the object key and generate the link at read time.
For response caching, set an explicit policy that matches the link lifetime. A private document should not become reusable through an intermediary cache merely because the object endpoint returned a permissive header. MDN's Cache-Control reference is a useful check when reviewing this boundary.
One small implementation detail matters more than it looks: object keys should be immutable for a document revision. If a user replaces a file, write a new key and update the relational pointer in one controlled operation. That makes retries easier to reason about and avoids silently serving old bytes under a reused name.
A minimal server-side write pattern
The write path should use a server-held credential, an explicit method, an idempotency key, response-status checks, and bounded backoff for rate limits. Link issuance and authorization belong in the surrounding application flow.
This is the smallest useful check I would run before wiring the storage call into a document service. The host is assembled from parts so this independent comparison does not publish a vendor link; the request itself remains ordinary Python and is runnable with requests installed.
import os
import time
import uuid
import requests
def put_document(bucket: str, key: str, content: bytes) -> dict:
host = "https://api." + "infrai" + ".cc"
url = f"{host}/v1/storage/object/put/{bucket}/{key}"
headers = {
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
"Content-Type": "application/octet-stream",
}
for attempt in range(4):
response = requests.request("PUT", url, headers=headers, data=content, timeout=60)
if response.status_code == 429:
wait = response.headers.get("Retry-After")
time.sleep(float(wait) if wait else 2**attempt)
continue
if not response.ok:
raise RuntimeError(f"storage write failed ({response.status_code}): {response.text}")
return response.json()
raise RuntimeError("storage write rate-limited after retries")
For browser-direct uploads, validate the provider's CORS controls before committing to the flow. A design that cannot be configured for the required origin is a deployment blocker, regardless of how pleasant the SDK feels.
Where the simple choice stops being enough
The unified REST option has meaningful boundaries for this use case. It has no public-read ACL, so it is unsuitable for a permanent public link, static-site hosting, or an image host. It has no object versioning or object lock, so an overwritten object is not recoverable through those features; regulated, tamper-resistant retention needs an external design. Strict conditional writes also need coordination through a queue or database because there is no If-Match write condition.
There are operational edges too. CORS cannot be independently configured through a dedicated route, lifecycle expiration is no shorter than one day, and there is no automatic cleanup rule for multipart fragments. Metadata is not server-searchable beyond prefix filtering. Cross-region replication and cross-cloud batch migration are absent; the covered providers include R2, S3, OSS, and COS, but not GCS or B2. Trial credit cannot pay for persistent writes.
Those are not reasons to reject it universally. They are reasons to keep S3 or a provider with the missing control when retention, browser-direct uploads, or migration is a first-order requirement. The same checklist should be applied to Supabase and Firebase rather than hidden behind a feature-count comparison.
A rollout decision I can defend
Choose Supabase when relational ownership checks and server-side simplicity dominate. Choose Firebase Storage when mobile upload feedback and real-time client behavior dominate. Choose S3 when the organization needs the broadest object-storage operating model and is willing to own the integration layer.
Before production, upload files at the 95th percentile size, interrupt connections, retry the same write, and download from the regions customers actually use. Verify that a deleted document stops generating new links, that an old link expires as intended, and that egress is visible in the operational budget. Storage selection becomes much less mysterious after those tests.
Top comments (0)