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 }
Create and replace conditions
The single-key form accepts a provider-native create or exact-replace condition. A create succeeds only when the destination is absent; a replace succeeds only while its supplied bare strong ETag is current. The SDK does not probe first, and unsupported adapters fail before provider I/O.
const created = await files.upload("reports/q3.json", body, {
condition: { type: "create" },
});
const replaced = await files.upload("reports/q3.json", nextBody, {
condition: { type: "replace", etag: created.etag },
});
Conditional uploads return a non-optional ETag. They are not available for array, multipart, resumable, or signed-upload paths; the initial S3 implementation also rejects ReadableStream bodies before I/O, so buffer to a Blob or Uint8Array first. See Conditional operations for capabilities, plugin behavior, and ambiguous retry outcomes.
Options
condition?UploadCondition
Apply the upload atomically only when the destination is absent (`create`) or still has the supplied strong ETag (`replace`). Conditional uploads require a native adapter primitive and are unavailable for bulk, multipart, and resumable uploads.
UploadConditioncontentType?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, WebDAV, 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, WebDAV, 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. Fire-and-forget: a throw from it is swallowed and never fails (or retries) the upload. 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 (on the `aws-sdk` client) 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. The one exception is the S3 `fetch` client (`s3Fetch()`, and R2 / MinIO / RustFS with `client: "fetch"`), which has no multipart path and **throws** rather than silently buffering the body into a single PUT.
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 (on the `aws-sdk` client), Bun's S3, GCS, Firebase Storage, Azure Blob, Google Drive, OneDrive, SharePoint, Dropbox, Box, Vercel Blob, Supabase, Appwrite, Cloudinary, FTP, SFTP, `fs`, and the in-memory adapter; others (including the `fetch` S3 client) throw. Check {@link AdapterCapabilities.multipart} to branch at runtime. 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
Timeout in milliseconds, applied to each attempt (not the call as a whole). 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 with the
"aws-sdk"client) 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 with the
- The remaining adapters (Supabase, Convex, Dropbox, Box, OneDrive, Google Drive, SharePoint, Cloudinary, Bunny, Appwrite, PocketBase, Netlify Blobs, UploadThing, SFTP, WebDAV, Bun S3, the R2 Workers binding, and the
fetchS3 engine) 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 with the
"aws-sdk"client) 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, SharePoint, 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