Skip to content
Files SDK
Esc
navigateopen⌘Jpreview
On this page

signedUploadUrl

A presigned PUT-or-POST contract so a browser can upload straight to the bucket - bandwidth and CPU stay off your server.

files.signedUploadUrl(key, options)

Returns a discriminated PUT-or-POST contract so a client (typically a browser) can upload directly to the bucket without proxying bytes through your server. The flow is: your server calls signedUploadUrl(), returns the result to the browser, the browser uploads straight to the provider directly. Bandwidth and CPU stay off your server.

Without maxSize, the adapter returns a presigned PUT URL - simpler, but with no server-side size cap. With maxSize, providers that support upload policies switch to a presigned POST form whose policy enforces the size at the bucket via content-length-range. In practice you should pass maxSize when the adapter supports it - without it, anyone with the URL can DoS your storage costs until expiresIn elapses.

Vercel Blob, Bunny Storage, Appwrite, PocketBase, fs, and Convex throw here - Vercel’s upload model goes through handleUpload() from @vercel/blob/client instead of presigned URLs, Bunny Storage writes require the Storage API AccessKey header, Appwrite/PocketBase have no presigned upload primitive at all, fs has no signer/verifier-backed upload server, and Convex upload URLs cannot bind the caller’s SDK key or constraints. The R2 Workers binding throws unless you’ve configured hybrid mode (binding + HTTP credentials). Azure, Supabase, R2, Google Drive, OneDrive, SharePoint, Cloudinary, and UploadThing have no content-length-range equivalent and throw if you pass unsupported size limits; omit those options for a presigned PUT/session URL and enforce upload caps at your application gateway instead. Azure also throws if you pass contentType, because SAS does not bind Content-Type into the signature.

// On your server: hand back an upload contract that lets the browser
// PUT/POST the file directly to the bucket. Bytes never touch your server.
const upload = await files.signedUploadUrl("avatars/abc.png", {
  expiresIn: 60,
  contentType: "image/png",
  maxSize: 5_000_000,
});
// → { method: "PUT", url, headers? }
//   | { method: "POST", url, fields }

// In the browser: PUT path (no maxSize) is a plain fetch.
await fetch(upload.url, {
  method: "PUT",
  body: file,
  headers: upload.headers,
});

// POST path (with maxSize) is multipart with the signed policy fields.
const form = new FormData();
for (const [k, v] of Object.entries(upload.fields)) form.append(k, v);
form.append("file", file);
await fetch(upload.url, { method: "POST", body: form });

Options

Could not generate a type table for SignUploadOptions: Cannot read properties of undefined (reading 'ESNext')

Was this page helpful?