Remix File Upload

Upload Files from Remix Action Functions with fetch

Upload files from Remix action functions with fetch. No SDK, REST API, CDN delivery.

In Remix, the action function is where mutations live, and it runs only on the server — which makes it the natural home for a file upload that must keep its API key secret. files.link needs three fetch calls there: POST metadata, PUT bytes, POST confirm. No aws-sdk, no platform-specific storage binding, and the same action code runs whether you deploy to a Node server, Vercel, or Cloudflare Pages.

The Remix-specific wrinkle is multipart parsing. A browser form with enctype='multipart/form-data' is parsed with unstable_parseMultipartFormData (or the newer parseFormData helpers), using a memory or upload handler to get the file out. Once you have the File, you read its bytes and PUT them.

ts
import { unstable_parseMultipartFormData, unstable_createMemoryUploadHandler } from '@remix-run/node';

export async function action({ request }: ActionFunctionArgs) {
  const form = await unstable_parseMultipartFormData(
    request, unstable_createMemoryUploadHandler());
  const file = form.get('upload') as File;
  const key = process.env.FILESLINK_KEY!;

  const r1 = await fetch(`https://api.files.link/v1/files/${process.env.FOLDER_ID}`, {
    method: 'POST',
    headers: { Authorization: key, 'Content-Type': 'application/json' },
    body: JSON.stringify({ filesMetadata: [{ name: file.name, size: file.size, type: file.type }] }),
  });
  const slot = (await r1.json()).urls[0];

  await fetch(slot.url, { method: 'PUT', headers: { 'Content-Type': file.type || 'application/octet-stream' }, body: await file.arrayBuffer() });

  await fetch('https://api.files.link/v1/files/confirm-upload', {
    method: 'POST',
    headers: { Authorization: key, 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: [slot.id] }),
  });
  return json({ id: slot.id });
}

Return the new file id (or CDN URL) from the action and Remix revalidates your loaders automatically, so the UI updates without a manual refetch. On Cloudflare Pages, the only thing to watch is that the multipart helper you import is the one compatible with the Workers runtime — fetch itself is native there. For large files, prefer streaming the upload to the presigned URL or doing a direct browser-to-storage PUT and only calling confirm-upload from the action, which keeps you under platform request limits.

The API key stays in process.env, never in a loader that ships data to the client. Files return as CDN URLs with per-file visibility, encrypted at rest, billed from prepaid credits.

Benefits With No Complexity

Global CDN delivery

Your files are served from 450+ edge locations worldwide.

Edge-cached worldwide

Files are cached close to your users for fast delivery.

Signed-URL security

Private files stay protected with time-limited access.

What You Get

Unlimited files

Upload as many files as you need.

Unlimited storage

There is no storage limit.

Public + Private storage

Private files are fully secured.

CDN ready links

Upload directly to 450+ edge locations worldwide.

Prepaid credits

No subscription. Pay only for the storage and bandwidth you use.

More coming soon

We have plenty of features coming!

How files.link Works

1. Upload

Create a project and upload your first file.

2. Copy

Copy the CDN link.

3. Use Anywhere

Paste and enjoy the blazing speed.

Why Developers Choose files.link

A Better Way to Store & Deliver Files

AWS S3 (aws-sdk)Cloudflare R2 bindingfiles.link
Works in action() (no SDK install)
Same code on Node / Vercel / CF Pages
Auto loader revalidation after upload
Global CDN delivery on every file
Prepaid credits (no surprise bills)

Calculate Your Needs

Storage

0GB

Egress

0GB

CDN Bandwidth

0GB
Total: $0.000/month

Wire Up Remix File Uploads

  • Global CDN delivery
  • Edge-cached worldwide
  • Signed-URL security
  • Unlimited files
  • Unlimited storage
  • Public + Private storage
  • No subscription — prepaid credits keep spend predictable
Start Uploading from Remix

Frequently Asked Questions

Why upload from an action and not a loader?
Loaders are for reads and their data can be sent to the client; actions handle mutations and run server-side only. Putting the upload in an action keeps the API key off the client and follows Remix's data-flow model.
How do I parse the multipart form in Remix?
Use unstable_parseMultipartFormData with an upload handler, then form.get('upload') gives you a File. PUT its arrayBuffer() to the presigned URL — don't re-encode it as multipart.
Does this run on Cloudflare Pages?
Yes. fetch is native to the Workers runtime. The only caveat is to use a multipart parsing helper that is Workers-compatible; the three files.link calls themselves are unchanged across Node, Vercel, and CF Pages.
How does the UI update after upload?
Return the result from the action. Remix automatically revalidates the page's loaders after an action completes, so the new CDN URL appears without you writing a manual refetch.
How do I handle large uploads under platform limits?
Stream the file to the presigned URL, or have the browser PUT directly to the presigned URL and only call confirm-upload from the action. That keeps large request bodies out of your serverless function.
General Questions
Is there a subscription?
No. files.link uses prepaid credits, so storage, bandwidth, and API usage are deducted from your balance as you go. The app explains the payment step before uploads are enabled — no monthly subscription and no per-user fees.
What is files.link?
files.link is a developer-first file platform that makes it simple to store, secure, and deliver files globally. It provides signed URLs, public/private access, and an API-first design so you can integrate file delivery into any app without the usual complexity.
How does billing work?
files.link uses prepaid credits. As you use storage, bandwidth, and API requests, credits are deducted daily. When your balance runs low, we automatically recharge it using your saved card. If an auto-recharge doesn't go through, your files and links stay put — you simply update your payment method or top up manually to keep going. No monthly subscriptions — just simple usage-based pricing.
How secure is my data?
All files are encrypted at rest and in transit. You can use signed URLs for private files, control access with permissions, and rely on enterprise-grade infrastructure for data protection.

Related

files.link
Copyright © 2026
All rights reserved
ContactGuidesGlossaryStatusLegal