Cloudflare R2 Alternative When You Want an Upload API, Not a Storage Project
The short answer: Cloudflare R2 is excellent object storage. FilePost is a finished file-upload product. Choose R2 when storage infrastructure is something your team wants to own. Choose FilePost when storage infrastructure is something your team wants to stop thinking about.
R2's pricing is hard to beat: low per-GB storage, free Internet egress and generous free operation allowances. But an R2 bucket is not automatically a production upload API with durable public URLs. Your application still owns credentials, S3 request signing, object keys, CORS, public access, custom-domain setup, caching policy, retries, metadata and lifecycle behavior.
Cloudflare R2 vs FilePost
| Decision | FilePost | Cloudflare R2 |
|---|---|---|
| Product layer | Managed upload and public delivery API | S3-compatible object storage |
| First upload | One multipart request | S3 SDK/Worker or presigned PUT flow |
| Production public URL | Returned immediately | Configure a custom domain and public bucket behavior |
| Browser upload | Use trial journey or your server-side API integration | Generate presigned URL and configure bucket CORS |
| Pricing | Plans based on upload count, storage and file size | Storage plus Class A/B operations; Internet egress free |
| File management | List, inspect and delete through FilePost API/dashboard | Own S3 object operations and management UI |
| Automation | Direct signed HTTP webhook with retries | Event notifications and Queues/Workers architecture |
| Control | Opinionated managed service | Bucket ownership and infrastructure control |
What It Takes to Turn R2 into "Upload and Get a URL"
A production browser-to-R2 upload commonly needs two requests and several infrastructure decisions:
- Create the R2 bucket and scoped API credentials.
- Choose an object-key convention and collision strategy.
- Build a backend endpoint or Worker that signs a
PutObjectrequest. - Configure CORS for the browser origin.
- Return the temporary presigned PUT URL to the browser.
- Upload the bytes to that URL.
- Configure public read access separately.
- Connect a custom domain for production delivery.
- Construct and store the final public object URL.
- Add validation, retries, metadata, cleanup and event handling.
Cloudflare's official presigned URL documentation lists the required account ID, bucket, object path, operation, expiry, R2 credentials and AWS Signature Version 4 implementation. It also notes that presigned URLs work with the S3 API domain, not custom domains.
FilePost makes the product-level operation one request:
curl -X POST https://upload.filepost.dev/v1/upload \
-H "X-API-Key: $FILEPOST_API_KEY" \
-F "file=@build-artifact.zip"
The response includes the public CDN URL. No bucket name, account ID, region placeholder, object URL assembly or second upload request is required.
Where R2 Is Better
- Raw cost at scale. R2's standard storage was $0.015 per GB-month when checked August 9, 2026, with free Internet egress.
- Direct bucket ownership. You control keys, metadata, storage classes, lifecycle and access architecture.
- S3 ecosystem compatibility. Existing AWS SDKs, CLI tools and data pipelines can target the R2 endpoint.
- High-scale infrastructure. R2 is the better primitive when millions of object operations and custom cache behavior are normal engineering work.
- Large objects and multipart uploads. FilePost currently caps Pro uploads at 500 MB.
- Private or custom access controls. Workers, WAF, Access and signed request designs can implement requirements beyond public-link delivery.
If your team already knows S3 and Cloudflare, FilePost may be an unnecessary abstraction. FilePost is selling avoided engineering and operational work, not cheaper object bytes.
Where FilePost Is Better
- Time to first working URL. One API key and one multipart request.
- A production delivery URL by default. You do not need to expose a development domain or connect your own zone.
- Application-level limits. Plans talk about uploads and file sizes instead of Class A and Class B operations.
- Built-in account workflows. Dashboard, file listing, deletion, API-key rotation, intake links and signed upload events are already product features.
- Framework-independent recipes. The same request works in cURL, Python, Go, PHP, Ruby and automation tools.
- No storage credentials in application code. Your application uses a FilePost key rather than owning S3 credentials and signing logic.
Public Delivery Is a Separate R2 Decision
R2 buckets are private by default. Cloudflare's public bucket documentation offers two exposure methods:
- An
r2.devsubdomain intended for non-production use and subject to rate limiting. - A custom domain for production access, caching and security controls.
Cloudflare also notes that only certain file types are cached by default unless you configure broader cache behavior. R2 gives you the pieces; you decide how the public delivery product should work.
Price Comparison Without Pretending the Products Are Equal
The official R2 pricing page, checked August 9, 2026, listed:
- 10 GB-month standard storage free each month.
- 1 million Class A operations free each month.
- 10 million Class B operations free each month.
- $0.015 per GB-month standard storage beyond the allowance.
- $4.50 per million Class A and $0.36 per million Class B operations.
- Free Internet egress.
FilePost Lite is $4 per month for 300 uploads, 100 MB files and 10 GB storage. Starter is $9 for 1,500 uploads and 200 MB files. R2 will often be cheaper in raw infrastructure dollars. FilePost can be cheaper in engineering time when bucket and delivery configuration are not part of your product's advantage.
Code Comparison: Direct Browser Upload
R2 backend: generate the presigned URL
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const r2 = new S3Client({
region: "auto",
endpoint: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
const key = `uploads/${crypto.randomUUID()}-${fileName}`;
const uploadUrl = await getSignedUrl(
r2,
new PutObjectCommand({
Bucket: "uploads",
Key: key,
ContentType: contentType,
}),
{ expiresIn: 900 }
);
R2 browser: upload the bytes
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
const publicUrl = `https://files.example.com/${key}`;
FilePost server: upload and receive the URL
const form = new FormData();
form.append("file", file);
const response = await fetch("https://upload.filepost.dev/v1/upload", {
method: "POST",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY },
body: form,
});
const { url, file_id } = await response.json();
Migration Checklist
- Identify the paths that only use
PutObjectplus a public URL. - Keep R2 for private objects, very large files or high-scale storage pipelines.
- Replace the signing endpoint and client PUT with one FilePost server request.
- Store FilePost's returned URL and file ID instead of constructing an object URL.
- Replace queue/Worker notifications with the signed FilePost webhook where appropriate.
- Leave old R2 objects and custom-domain URLs available until references are migrated.
The Decision Rule
Choose R2 when object storage is a core infrastructure competency and cost/control at scale outweigh setup work.
Choose FilePost when file handling is a supporting feature and your real requirement ends at a durable public URL.
Compare the outcome, not the architecture
The upload form above creates a real URL without a bucket or signing endpoint. If that is the complete result your feature needs, FilePost is the smaller integration.
Get a free API key