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
SignUploadOptions: Cannot read properties of undefined (reading 'ESNext')