upload
Write a body to a key - a single object or many in one call, with optional progress tracking and multipart uploads.
files.upload(key, body, options?) · files.upload(items)
Writes a body to key. Accepts native File, Blob, ReadableStream, ArrayBuffer, or string. Content type is inferred from the input when possible.
await files.upload("avatars/abc.png", file, {
contentType: "image/png",
cacheControl: "public, max-age=31536000",
metadata: { userId: "123" },
});
// → { key, size, contentType, etag, lastModified }
Options
contentType?string
MIME type stored alongside the object and returned to readers in the `Content-Type` response header. Inferred from `File` / `Blob` `type` when not set; falls back to `application/octet-stream`.
stringcacheControl?string
`Cache-Control` header stored on the object. Sent verbatim to the provider; controls how downstream caches and browsers cache reads of this key. **Throws** a {@link FilesError} on adapters with no cache-control field (FTP, SFTP, Dropbox, Box, OneDrive, SharePoint, Cloudinary, Appwrite, PocketBase, Bunny Storage, Convex, UploadThing, Bun's S3) rather than silently dropping it — check {@link Adapter.supportsCacheControl} to branch at runtime.
stringmetadata?Record<string, string>
Arbitrary user metadata stored alongside the object. Returned by `head()` and `list()` where the provider supports it. **Throws** a {@link FilesError} on adapters with no user-metadata primitive (Vercel Blob, UploadThing, FTP, SFTP, Dropbox, Box, OneDrive, SharePoint, Cloudinary, Appwrite, PocketBase, Bunny Storage, Convex, Bun's S3) rather than silently dropping it, mirroring the {@link DownloadOptions.range} gate. An empty object is treated as "no metadata" and never throws. Check {@link Adapter.supportsMetadata} to branch at runtime.
Record<string, string>onProgress?(progress: UploadProgress) => void
Called as the upload makes progress, for driving a progress bar. Granularity depends on the body and the adapter: - A `ReadableStream` body is reported byte-by-byte as the adapter consumes it (`total` is omitted unless the length is known). - A buffered body (`File`, `Blob`, `ArrayBuffer`, `Uint8Array`, `string`) is handed to the provider whole, so it reports `{ loaded: 0, total }` then `{ loaded: total, total }` — unless the adapter reports true progress itself (see below). - **S3 and the S3-compatible adapters** report true byte-level progress for every body type (including multipart for large files). This path uses `@aws-sdk/lib-storage`, an optional peer dependency that must be installed when `onProgress` is used with those adapters. Only fires while the upload is in flight and on success; a failed upload does not emit a final event. On retry, progress restarts.
(progress: UploadProgress) => voidmultipart?boolean | MultipartOptions
Upload the body in parallel parts instead of a single request. Pass `true` for sensible defaults (5 MiB parts, 4 in flight), or an object to tune `partSize` / `concurrency`. Multipart is the robust path for large objects and for `ReadableStream` bodies of unknown length: a single PUT must buffer or know the length up front, while multipart streams part-by-part. On S3-family adapters this routes through `@aws-sdk/lib-storage` (an optional peer dependency) and is **auto-engaged for unknown-length streams** even when this flag is unset. OneDrive, GCS, Firebase, and Azure map it to their native chunking; other adapters already stream or chunk transparently and ignore it.
boolean | MultipartOptionscontrol?UploadControl
Drive the upload through a pause-able, resumable session. Construct an {@link UploadControl}, pass it here, and call `pause()` / `resume()` / `abort()` on it; persist `control.toJSON()` and rehydrate with `UploadControl.from(token)` to resume in a later process. Requires a body with a known length (`File`, `Blob`, `ArrayBuffer`, a typed array, or `string`) — a `ReadableStream` can't be re-read to resume. Supported on S3 and the S3-compatible adapters, GCS, Firebase Storage, Azure Blob, OneDrive, and Dropbox; other adapters throw. Not available in the array (bulk) form of `upload`.
UploadControlsignal?AbortSignal
Abort the operation when this signal is aborted. When both constructor and per-call signals are provided, either one can abort the call.
AbortSignaltimeout?number
Overall timeout in milliseconds, applied to each attempt. A timeout aborts the operation and is not retried. `0` or a negative value disables timeout handling.
numberretries?RetryOptions
Retry provider failures. A number is treated as `{ max: number }`.
RetryOptionsProgress tracking
Pass onProgress to drive a progress bar as bytes are sent:
await files.upload("big.zip", stream, {
onProgress: ({ loaded, total }) => {
const pct = total ? Math.round((loaded / total) * 100) : null;
console.log(pct === null ? `${loaded} bytes` : `${pct}%`);
},
});
Every adapter calls onProgress. How fine-grained it is depends on the body and the adapter:
- A
ReadableStreambody is reported byte-by-byte on every adapter, as the bytes are consumed. Its length is unknown, sototalis omitted — you getloadedonly. - A buffered body (
File,Blob,ArrayBuffer,Uint8Array,string) is handed to the provider whole, so by default it reports{ loaded: 0, total }then{ loaded: total, total }. - Some adapters report true byte-level progress for every body type (buffered included) by tapping their SDK’s native upload-progress hook: S3 and the S3-compatible adapters, R2 (HTTP), Azure Blob, Google Cloud Storage, Firebase Storage, Vercel Blob, and FTP. Notes:
- The S3 family (incl. R2 over HTTP) needs the optional
@aws-sdk/lib-storagepackage installed; it also enables multipart for large files. - GCS and Firebase Storage switch to a resumable upload when
onProgressis set (only that path emits progress) — one extra round trip versus the default simple upload.
- The S3 family (incl. R2 over HTTP) needs the optional
- The remaining adapters (Supabase, Convex, Dropbox, Box, OneDrive, Google Drive, SharePoint, Cloudinary, Bunny, Appwrite, PocketBase, Netlify Blobs, UploadThing, SFTP) send buffered bodies in a single request with no progress signal, so those report only the start/finish pair above. Stream bodies still get byte-level.
onProgress fires only while the upload is in flight and on success; a failed upload emits no final event, and a retry restarts progress. In the array form, each report also carries the item’s key.
Multipart uploads
Pass multipart to upload a large body in parallel parts instead of a single request — the robust path for objects beyond the single-request limit (5 GB on S3) and for ReadableStream bodies of unknown length:
// Defaults: 5 MiB parts, 4 in flight.
await files.upload("backups/db.tar", stream, { multipart: true });
// Or tune it:
await files.upload("backups/db.tar", stream, {
multipart: { partSize: 16 * 1024 * 1024, concurrency: 8 },
});
- S3 and the S3-compatible adapters (incl. R2 over HTTP) run multipart through the optional
@aws-sdk/lib-storagepackage, falling back to a singlePutObjectwhen the body fits in one part. Unknown-length streams use multipart automatically, even without the flag. - OneDrive uploads above 250 MB (and any
multipartrequest) go through a chunked upload session — large files that previously failed now just work. - GCS and Firebase Storage switch to a resumable upload;
partSizemaps to the chunk size. - Azure Blob already splits large bodies into parallel blocks;
multipartonly tunes the block size and concurrency. - Dropbox streams
ReadableStreambodies through its upload session chunk-by-chunk, so a large stream is never buffered whole;partSize(rounded to a 4 MiB multiple) tunes the chunk size. - Other adapters already stream natively or only accept a fully-buffered body, so they ignore the option.
Pause and resume
Pass a control (UploadControl) to pause, resume, or abort a large upload — and to resume it later, even in a new process, from a serializable session token. It’s supported on every adapter whose provider exposes a resumable session — S3 and the S3-compatible adapters, GCS, Firebase Storage, Google Drive, Azure, OneDrive, Dropbox, Vercel Blob, the local filesystem, FTP/SFTP, Supabase, Appwrite, and Cloudinary (Box, bun-s3, and memory pause in-process only); the rest throw. See Resumable uploads for the full walkthrough.
import { UploadControl } from "files-sdk";
const control = new UploadControl();
const result = files.upload("big.iso", file, { control });
control.pause();
control.resume();
await result;
Many items
Pass an array of { key, body, ...options } to upload many in one call. Each item carries its own contentType / cacheControl / metadata / multipart. The call returns a structured result instead of throwing on partial failure: successes land in uploaded, per-item failures (including invalid keys) in errors, both in the order supplied. It honors the client’s prefix and fans out with bounded concurrency (default 8); stopOnError: true stops at the first failure.
const result = await files.upload(
[
{ key: "avatars/a.png", body: a, contentType: "image/png" },
{ key: "avatars/b.png", body: b },
],
{ concurrency: 8, stopOnError: false }
);
result.uploaded; // UploadResult[] — successes, in the order supplied
result.errors; // undefined when every item succeeded
Item (array form)
keystring
stringbodyBody
BodycontentType?string
Per-item MIME type. See {@link UploadOptions.contentType}.
stringcacheControl?string
Per-item `Cache-Control`. See {@link UploadOptions.cacheControl}.
stringmetadata?Record<string, string>
Per-item user metadata. See {@link UploadOptions.metadata}.
Record<string, string>multipart?boolean | MultipartOptions
Per-item multipart toggle/tuning. See {@link UploadOptions.multipart}.
boolean | MultipartOptionsOptions (array form)
onProgress?(progress: UploadProgress & { key: string }) => void
Called as each item makes progress. Same semantics as {@link UploadOptions.onProgress}, with the item's `key` added so callers can attribute the report to a file when several upload concurrently.
(progress: UploadProgress & { key: string }) => voidconcurrency?number
How many per-key operations run in parallel. Defaults to `8`. Ignored when `stopOnError` is set — that path runs sequentially.
numberstopOnError?boolean
When `true`, stop at the first failure and return immediately with the results gathered so far plus that error. When `false` (default), process every item and collect per-key failures in `errors`.
boolean