Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

files-sdk@2.6.1

Patch Changes

  • add46b5: The downloadFile tool in files-sdk/ai-sdk, files-sdk/openai, and files-sdk/claude now enforces maxBytes on the bytes it actually reads. It used to check only the size head() reported and then read the whole body, so an object replaced between the two calls, or a size that under-reported the body, bypassed the cap. The body is now streamed and the download is cancelled with the same maxBytes error as soon as it passes the limit.
  • 5ed8b95: files-sdk/api proxied downloads now send Accept-Ranges: none when the adapter can’t serve byte ranges, instead of always advertising bytes.
  • 5ed8b95: files-sdk/api proxied downloads now honour If-Range. A resumed download whose validator (entity tag or date) no longer matches the object gets the whole new object with a 200. Before, it got a 206 slice of the changed object that the client spliced onto the old bytes. Weak entity tags never satisfy If-Range.
  • 5ed8b95: files-sdk/api proxied downloads now send X-Content-Type-Options: nosniff, so a browser won’t sniff stored bytes into an executable type when a download is served inline.
  • 5ed8b95: files-sdk/api no longer applies the cross-origin (CSRF) check to the bulk head request. Like head and exists, it is a read, and the Origin check is only for requests that change storage.
  • 5ed8b95: files-sdk/api docs fixes. defaultExpiresIn is documented as what it is: the expiry used when the client doesn’t ask for one. It was described as a ceiling, but a client can request a longer expiresIn, so cap that with authorize’s maxExpiresIn, which is now documented to cover upload URLs too. A stale module comment that said only the Next.js binding existed now lists every framework binding.
  • 5ed8b95: files-sdk/api purge with no key (empty the trash) no longer deletes trash entries that an authorize filterKeys hides when there is no keyPrefix. It used to call the plugin’s bare purge(), emptying the whole trash. Now it purges only the entries the caller can see, as it already did under a keyPrefix.
  • 5ed8b95: files-sdk/api now bounds the work a single request can ask for. Bulk keys[], presign files[] and complete completions[] over maxBatchSize (default 1000) get 413 with reason count. A client-supplied bulk concurrency is clamped to maxConcurrency (default 16), and a search page limit to maxListLimit. A JSON body over maxJsonBodySize (default 1 MiB) gets 413 without being buffered. All three limits are new createFilesRouter options.
  • 5ed8b95: files-sdk/api now refuses search patterns that could tie up the server. A glob such as *a*a*a*a*a*a*a*a*a*a*b or a flat regex chain such as ^(.*a){10}.*b$ could take seconds to test against a single key on the backtracking regex engine, and the old check only caught nested repetition. The gateway now answers 422 before matching any key when a pattern is longer than maxSearchPatternLength (default 256 characters) or has more than maxSearchWildcards unbounded wildcards or quantifiers (default 4; an unanchored regex counts one extra). Both limits are new createFilesRouter options.
  • 82d82e0: files-sdk/appwrite now uploads an empty body through a resumable (control) upload as a single createFile request. It used to send the invalid chunk header Content-Range: bytes 0--1/0.
  • 82d82e0: files-sdk/appwrite upload() and copy() now overwrite an existing key instead of failing with Conflict. Appwrite can’t update a file’s content, so the existing file is deleted and created again; this isn’t atomic, and file-level permissions on the old file aren’t carried over.
  • 82d82e0: files-sdk/appwrite url() no longer silently ignores responseContentDisposition. A bare "attachment" now returns the /download URL, which Appwrite serves as an attachment; any other value (a custom filename, inline) throws, since Appwrite has no per-request override. Ignoring it served user uploads inline even when a download was asked for.
  • 5981d9d: files-sdk/audit now records error.applied: true when a conditional mutation committed at the provider and a plugin inside audit() then rejected the call. The core marked the error as applied only after it had left the plugin chain, so audit() (and any other wrapping plugin) saw a plain failure for a write that had actually landed.
  • b47b9cc: copy() in files-sdk/azure now works with a credential and useUserDelegationSas: false. The copy source is authorized with the credential’s bearer token, where it was previously sent unauthenticated and rejected for private containers.
  • b47b9cc: Explicit auth options passed to files-sdk/azure (connectionString, accountKey, credential, sasToken) now always win over environment variables: AZURE_STORAGE_CONNECTION_STRING, AZURE_STORAGE_ACCOUNT_KEY / AZURE_STORAGE_KEY, and AZURE_STORAGE_SAS_TOKEN are read only when none of those options is passed. An explicit endpoint is also honored when a connection string carries an account key or SAS, instead of being ignored. The missing-credentials error now lists every accepted option and environment variable.
  • b47b9cc: Buffered ranged downloads from files-sdk/azure now clamp a range end past the end of the blob to the blob’s size, as streamed downloads and other adapters already do, instead of failing.
  • b47b9cc: files-sdk/azure now reports signedUrl.supported: false when it has no signer (SAS-only, anonymous, or credential with useUserDelegationSas: false), where url() and signedUploadUrl() always throw. Previously it always reported true.
  • b47b9cc: Signing failures in files-sdk/azure url(), such as a 403 when fetching a user delegation key, are now mapped to a FilesError (for example Unauthorized) instead of escaping as the raw Azure SDK error.
  • b47b9cc: User Delegation SAS URLs from files-sdk/azure can no longer outlive the delegation key that signs them. With a credential, url() and signedUploadUrl() now throw for an expiresIn above 7 days instead of returning a URL that stops working when its key expires, and files.capabilities.signedUrl.maxExpiresIn reports the 7-day cap so the gateway clamps to it.
  • 33891e7: The files-sdk/backblaze-b2 region JSDoc lists us-west-004 instead of us-east-004, which isn’t a Backblaze B2 cluster.
  • aefa047: files-sdk/box now lists the folder a prefix points into. list() only ever read rootFolderId and compared the whole prefix against child names, so list({ prefix: "photos/", delimiter: "/" }) came back empty, the folder prefixes it returned led nowhere, and a Files client prefix listed nothing. The part of the prefix up to its last / now picks the folder (resolved under rootFolderId), the rest filters child names, and keys come back in full (photos/cover.jpg); a prefix into a missing folder lists nothing.
  • aefa047: files-sdk/box now pages folder listings by marker instead of offset, in list() and in the key lookups every other method runs. Box rejects an offset above 10,000, so folders larger than that could neither be listed past that point nor have their later files found. list() cursors are now Box’s opaque markers; numeric cursors from earlier versions are no longer accepted.
  • aefa047: files-sdk/box now reports capabilities.signedUrl.supported: false when built with publicByDefault or publicBaseUrl, since url() returns a permanent public link in those modes rather than a signed one. The defaultUrlExpiresIn docs now say it is accepted for API symmetry but not honoured, because Box controls the download URL’s lifetime.
  • c7aa8aa: FilesError now matches across bundled copies of the class. The package root, the edge entries (files-sdk/api, files-sdk/client, …) and each framework binding bundle their own copy, so instanceof FilesError failed whenever an error crossed entry points: a files-sdk/api gateway answered 500 Provider for every FilesError raised by Files (a missing key returned 500 instead of 404) or thrown from authorize (500 instead of 401), and errors from files-sdk/client or useFiles did not match the FilesError imported from files-sdk. Resolves #164.
  • 33f9f2e: files-sdk/bun-s3 now supports list({ delimiter }). Bun’s list forwards the delimiter and returns commonPrefixes, which the adapter now surfaces as prefixes, and it declares supportsDelimiter, so folder listing no longer throws.
  • 33f9f2e: files-sdk/bun-s3 now rejects a presigned url() or signedUploadUrl() with an expiresIn over 604800 seconds (7 days, the SigV4 limit), with the same permanent Provider error as the rest of the S3 family. Bun signed longer lifetimes without complaint, but the server rejected the URL when it was used. The adapter also declares capabilities.signedUrl.maxExpiresIn: 604800, so the useFiles gateway clamps expiry to it.
  • 33f9f2e: Range downloads on files-sdk/bun-s3 work on real Bun again. Bun’s S3Stats exposes its fields as getters, so spreading it copied nothing, lastModified came back undefined, and every ranged download() failed. The adapter now copies each field explicitly.
  • 33f9f2e: signedUploadUrl({ contentType }) on files-sdk/bun-s3 now rejects. Bun’s presigned PUT URLs sign only the host header (its type option becomes a response-content-type query parameter), so the Content-Type was never enforced and the returned header was advisory. Omit contentType, or use files-sdk/s3 or files-sdk/s3-fetch, which sign it. The useFiles gateway falls back to proxying such uploads through your server.
  • 82d82e0: files-sdk/bunny-storage delete() no longer reports success when the Storage API rejects the delete. The SDK’s file.remove() resolves false instead of throwing on 401, 403 and 5xx responses, and the adapter ignored it. A false result now probes the key: a missing key is still an idempotent no-op, a probe error surfaces, and a file that still exists throws.
  • fd0bd11: cache() (files-sdk/cache) now hands out copies of cached download bytes and metadata. Previously a cache hit returned the cached buffer and metadata object by reference, and stream() enqueued the cached buffer itself, so a caller mutating a chunk or a metadata field changed what every later hit returned.
  • fd0bd11: cache() (files-sdk/cache) now caps a cached url() at the lifetime the URL itself states (X-Amz-Expires on S3 and S3-compatible adapters, X-Goog-Expires on GCS) when that is shorter than the requested expiresIn. Before, a signedUrlPolicy({ maxExpiresIn: 60 }) placed after cache() could leave a 60-second URL cached for up to an hour. The files-sdk/cache and files-sdk/signed-url-policy docs now say to place signedUrlPolicy() before cache(), and the cache() docs list the defaultUrlExpiresIn option.
  • add46b5: files-sdk/claude no longer lets approval-gated writes skip approval. createClaudeFileTools() listed every tool in allowedTools, which the Claude Agent SDK runs without consulting canUseTool, so uploadFile, deleteFile, copyFile, and signUploadUrl ran unprompted even under the default requireApproval: true; allowedTools now lists only the tools that need no approval. The bundled canUseTool also denies tools that aren’t on its own MCP server (such as Bash, Write, or another server’s tools) instead of allowing them; compose your own callback to authorize those.
  • add46b5: The copyFile tool from files-sdk/claude (claudeCopyFile() and createClaudeFileTools()) is now annotated destructiveHint: true, since copying onto an existing destination overwrites it. It stays idempotentHint: true.
  • 3b64569: Corrected the files CLI help text. The program description now states the actual number of supported providers (computed from the provider registry) instead of “30+”, and --dry-run now says it writes nothing rather than claiming it makes no network calls, since sync --dry-run still lists both providers to build its plan.
  • 8bc864a: The files CLI’s configuration hints now name options the adapters actually read. The --config-json examples for Appwrite (key, bucket), Google Drive (oauth: {…}, rootFolderId), Box (oauth, ccg, jwt or developerToken), OneDrive and SharePoint (clientCredentials: {…}) replace flat keys that were ignored, and an Oracle Cloud error for a missing tenancy namespace now explains that it goes in --config-json. The registry also records --region, not --endpoint, as the required flag for Akamai, IBM Cloud Object Storage and Oracle Cloud.
  • 8bc864a: files --provider supabase now passes --public-base-url through to the adapter, so url() returns <publicBaseUrl>/<key> as the flag’s help text describes. Previously the flag was silently dropped for Supabase.
  • 3b64569: The files CLI and files-sdk/loader now reject --provider names like toString or constructor with the usual “unknown provider” error. They matched inherited object properties and crashed with “entry.load is not a function”.
  • 3b64569: files upload <key> in the files CLI now infers the content type from the key’s extension when --content-type isn’t given, as upload --dir already did per file. Single-key uploads previously stored application/octet-stream for everything. --dry-run echoes the resolved type.
  • 4313433: files-sdk/client keyless upload(file, { contentType }) now honours contentType. It used to presign with the file’s own type and ignore the option.
  • 4313433: files-sdk/client download(key, { range }) now rejects when the response isn’t 206 Partial Content. A gateway or storage host that ignores Range answers 200 with the whole object, which was returned as if it were the requested slice. This matches the server SDK.
  • 4313433: Upload state in files-sdk/client, files-sdk/react, files-sdk/vue and files-sdk/svelte now always settles. Each file’s FileUploadState is one object for the whole upload, and it ends "success" (keyless and keyed), "error" with state.error set, or "aborted". onProgress fires once more after the terminal status, and bulk upload([...]) now accepts onProgress with one state per item. In the hooks, uploads now accumulates one entry per file across every upload() call instead of showing only the latest one, and progress covers those entries. reset() clears the finished entries without zeroing isUploading while uploads are still running.
  • 24a0f92: files-sdk/cloudinary now uploads an empty body through a resumable (control) upload as a single-shot request. It used to send the invalid chunk header Content-Range: bytes 0--1/0.
  • 24a0f92: files-sdk/cloudinary now reads private and authenticated assets through a signed private_download_url. download() and the lazy bodies from head() and list() used to fetch the unsigned delivery URL, which Cloudinary answers with 401 for those types, so every read failed and was retried as a Provider error. An asset with no stored format now fails once with a non-retryable error, matching url().
  • 24a0f92: files-sdk/cloudinary now signs and sends the adapter’s delivery type on resumable (control) uploads and in signedUploadUrl() fields. With type: "private" or "authenticated", those uploads previously landed as public upload assets that the adapter’s own head(), list(), and download() could not find.
  • 24a0f92: files-sdk/cloudinary’s signedUploadUrl() now fails closed on constraints it cannot enforce: a positive minSize throws (like maxSize already did), and contentType throws instead of being signed as a content_type field that Cloudinary ignores. Omit contentType, or restrict formats with an upload preset’s allowed_formats.
  • ac6a6b0: files.capabilities now reports signedUrl.supported: false and rangeRead: false when files-sdk/compression is installed, matching what the plugin refuses. The files-sdk/api gateway picks its download path from these flags, so with the default downloadMode: "auto" over a signing adapter it now streams downloads through the instance instead of answering 500, and a Range request gets the non-range treatment (416, or the full body with onUnsupportedRange: "ignore") instead of a 500.
  • ac6a6b0: head() and list() results under files-sdk/compression now return the original bytes from their body accessors. They already reported the uncompressed size, but text(), arrayBuffer(), blob() and stream() returned the stored compressed bytes; they now lazily download the object back through the plugin and decompress it.
  • ac6a6b0: files-sdk/compression now refuses resumable uploads: an upload() with a control throws a permanent FilesError before any I/O, and files.capabilities reports multipart: false. Compressed output isn’t byte-for-byte stable across runtimes or versions (Node and Bun differ for the same input), so a session resumed in another process could splice two different compressed streams into an object that can’t be decompressed. multipart: true without a control still works.
  • 8dcd641: contentType() (files-sdk/content-type) no longer rejects or relabels legitimate files it used to misread. A real favicon.ico is accepted in onMismatch: "reject" mode (its registered image/vnd.microsoft.icon type now agrees with the sniffed image/x-icon); RSS, Atom, KML, GPX, XHTML and other *+xml files behind an <?xml prolog keep their declared type instead of being rejected or flattened to application/xml; and a leading <!-- … --> comment is skipped, so an SVG or XML file that opens with one is no longer labelled text/html. detectContentType() likewise reports what follows leading comments (for example image/svg+xml) rather than always text/html.
  • 8dcd641: contentType() (files-sdk/content-type) now stores the type it confirmed. When the bytes agree with the type the key’s extension implies, that type is forwarded to the adapter, so a photo.png of real PNG bytes is stored as image/png rather than the adapter’s default (application/octet-stream), matching how a mismatched upload was already relabeled. Bodies it can’t identify still keep an explicit or Blob type and otherwise the adapter’s default; the key’s extension alone is never stored.
  • 5981d9d: Bulk download([...]) now validates a byte range that a plugin injects into an item, exactly like a single download(). A malformed range, or any range on an adapter without range support, is reported as that item’s error instead of being passed to the adapter, which could silently return the whole object.
  • 5981d9d: Corrected several files-sdk type-definition docs: timeout applies per attempt, bulk uploads never retry (but plugin re-routed sub-operations do and fire onRetry), the per-adapter support lists for range, delimiter, metadata, cacheControl, and control, the SignedUrlCapability and SignUploadOptions.maxSize examples, the url() note on key encoding (public URLs percent-encode each key segment), and the sync() progress ordering.
  • 5981d9d: Plugins can now narrow what files.capabilities advertises through an optional capabilities(caps) hook on FilesPlugin. The Files#capabilities getter folds every installed plugin’s hook over the adapter-derived snapshot in plugins order (read-only clones from files.readonly() keep the narrowing), so a plugin that can’t honor presigned URLs or byte ranges end to end can say so up front instead of only failing at call time. The snapshot’s signedUrl is now a copy, so a hook can’t rewrite the adapter’s own declaration.
  • 5981d9d: A Files instance with a prefix now rejects a key made only of slashes ("/", "//") with the usual “key must be a non-empty string” error. Before, the leading slashes were stripped to an empty key, so upload("/", body) wrote the prefix’s own prefix/ folder-marker object.
  • ac6a6b0: files.capabilities now reports signedUrl.supported: false and every conditional flag as false when files-sdk/dedup is installed, matching what the plugin refuses; rangeRead still follows the adapter. The files-sdk/api gateway picks its download path from these flags, so with the default downloadMode: "auto" over a signing adapter it now streams downloads through the instance (with Range support) instead of answering 500.
  • ac6a6b0: files-sdk/dedup now reports the content’s SHA-256 hash as the etag of upload(), download(), head() and list() results. A pointer’s own ETag was identical for every key and never changed with the content, so sync() in its default "etag" mode skipped same-size edits between dedup instances (leaving the destination stale) and versioning() ids could collide.
  • ac6a6b0: head() and list() results under files-sdk/dedup now return the content from their body accessors instead of the empty pointer object. text(), arrayBuffer(), blob() and stream() lazily read the content-addressed blob, and stream() streams it rather than buffering.
  • ac6a6b0: files-sdk/dedup no longer forwards control and multipart to the pointer write. An UploadControl drives exactly one upload, so passing one to upload() threw on the pointer write after the blob had landed; control and multipart now apply to the blob write only, and when the content is already stored no bytes move and the control is left undriven.
  • ac6a6b0: files-sdk/dedup now refuses writes into its content store through the instance: an upload(), signedUploadUrl(), or a copy() / move() whose destination is inside the store prefix (.dedup/ by default) throws a permanent FilesError. Previously any caller who could write a key could overwrite .dedup/<sha256> and change what every pointer to that content returned. Reads of the store and deleting a blob (for a garbage-collection sweep) still pass through.
  • d4884f0: files-sdk/dropbox now maps Dropbox’s no_write_permission error to Unauthorized instead of Conflict.
  • d4884f0: files-sdk/dropbox’s url() now applies the 4-hour expiresIn cap only to temporary links. With publicByDefault or publicBaseUrl, a longer expiresIn used to throw with advice to set publicByDefault even when it was set. In those permanent-link modes capabilities.signedUrl now reports supported: false with no maxExpiresIn.
  • ac6a6b0: files.capabilities now reports signedUrl.supported: false and rangeRead: false when files-sdk/encryption is installed, matching what the plugin refuses. The files-sdk/api gateway picks its download path from these flags, so with the default downloadMode: "auto" over a signing adapter it now streams downloads through the instance instead of answering 500, and a Range request gets the non-range treatment (416, or the full body with onUnsupportedRange: "ignore") instead of a 500.
  • ac6a6b0: head() and list() results under files-sdk/encryption now return plaintext from their body accessors. They already reported the plaintext size, but text(), arrayBuffer(), blob() and stream() returned the stored ciphertext; they now lazily download the object back through the plugin and decrypt it, so a cache() head hit and a miss return the same bytes.
  • ac6a6b0: files-sdk/encryption now refuses resumable uploads: an upload() with a control throws a permanent FilesError before any I/O, and files.capabilities reports multipart: false. Each upload encrypts under a fresh random data key, so a session resumed in another process spliced parts of two different ciphertexts into an object that could never be decrypted. multipart: true without a control still works.
  • 5981d9d: files-sdk/failover no longer fails over on permanent errors by default. The core SDK’s pre-I/O rejections (an invalid key, or an option the adapter can’t honor such as metadata, cacheControl, range, delimiter, or control) are now FilesErrors with permanent: true, so an upload with metadata to a primary without metadata support throws instead of silently landing on a secondary. The failover docs no longer point to a replication() plugin that doesn’t exist.
  • 4279df6: files-sdk/fastify JSDoc example now calls app.removeAllContentTypeParsers() before adding the catch-all parser, matching the docs. Fastify’s built-in application/json and text/plain parsers take precedence over * and would otherwise consume the body the gateway needs.
  • b47b9cc: files-sdk/firebase-storage no longer lets GOOGLE_APPLICATION_CREDENTIALS override explicit credentials, and it now reads that variable through Application Default Credentials instead of cert(). Explicit serviceAccountPath and credentials options now always win over environment variables, and workload identity federation (external_account) and authorized_user credential files work instead of crashing at construction.
  • 244926c: files-sdk/fs list() no longer stops walking when a subdirectory is removed mid-listing. An ENOENT from any nested directory ended the whole walk, silently dropping every directory not yet visited; that directory is now skipped, and only a missing root lists as empty.
  • 244926c: files-sdk/ftp now maps connect and login failures like every other error, so a rejected login (530) is Unauthorized instead of a retryable Provider error that the client kept retrying. A failed connect or login also closes its control socket, which used to leak once per attempt.
  • 244926c: files-sdk/ftp delete() and delete([...]) no longer report success when the server refuses the delete. They passed basic-ftp’s ignore-errors flag, which swallows every FTP error reply, so “550 Permission denied”, “450 file busy” and “530 not logged in” all looked like a successful delete. Only a file that is really gone is now a no-op: after a 550 the adapter checks the parent listing, and a file that is still there throws Unauthorized; other error replies throw as usual.
  • 244926c: files-sdk/ftp list() no longer comes back empty when a subdirectory disappears (or answers “not found”) partway through the recursive walk: that directory is skipped and the rest is listed, while a missing root still lists as empty. A 450 reply (“file busy”) is now a retryable Provider error rather than NotFound.
  • 244926c: files-sdk/ftp resumable uploads (upload({ control })) now append to a <key>.fls-part staging file and rename it over the key when the upload completes. They used to write straight to the key: starting an upload deleted the existing file, a paused or crashed upload left a truncated file readable under the key, and abort() deleted the key. list() hides staging files, and writes to keys ending in .fls-part now throw.
  • d4884f0: files-sdk/google-drive now rejects keys, metadata entries, content types, and cacheControl values that overflow Drive’s 124-byte limit per appProperties entry (key and value, UTF-8) with a non-retryable error before any Drive call. Keys can be at most 117 bytes. These writes used to fail with a 400 that was retried as a transient Provider error.
  • d4884f0: files-sdk/google-drive now restores literal \n escapes in GOOGLE_DRIVE_PRIVATE_KEY to newlines, as files-sdk/firebase-storage already does, so a PEM key pasted into an env file or CI secret parses.
  • d4884f0: files-sdk/google-drive now maps Drive’s rateLimitExceeded and userRateLimitExceeded 403 responses to a retryable Provider error instead of Unauthorized, so they are retried with backoff as Google recommends. Other 403s still map to Unauthorized.
  • d4884f0: The rootFolderId docs in files-sdk/google-drive now describe the actual fallback order: GOOGLE_DRIVE_ROOT_FOLDER_ID, then driveId, then "root".
  • d4884f0: files-sdk/google-drive’s signedUploadUrl() on an existing key now clears the previous upload’s stored content type, cacheControl, and metadata. Drive merges appProperties on update, so head() kept reporting the old content type after the new bytes landed.
  • 4279df6: files-sdk/koa now passes the gateway the pre-mount URL (ctx.originalUrl). Under koa-mount, ctx.req.url has the mount prefix stripped, so the proxy-upload target the gateway builds pointed at the wrong path.
  • 6e9580b: files-sdk/netlify-blobs now infers the content type on upload the same way the other adapters do: a Blob or File keeps its own type and a string body is stored as text/plain; charset=utf-8. Previously every upload without an explicit contentType was recorded as application/octet-stream, so head() and download() reported the wrong type.
  • 6e9580b: files-sdk/netlify-blobs now classifies errors by the HTTP status on Netlify’s BlobsInternalError instead of only parsing it from the message. When Netlify sends an x-nf-error header the message no longer contains the status, so 404, 401/403 and 409/412 responses were reported as Provider errors rather than NotFound, Unauthorized and Conflict.
  • 6e9580b: files-sdk/netlify-blobs list() now returns a cursor when limit leaves entries unreturned and resumes from it, so listAll({ limit }) and paginated file browsers see every key instead of silently stopping after the first page. The cursor is the last key of the page (Netlify’s own cursor is internal to its SDK), and with delimiter a folder counts against limit like a file, matching the other key-list adapters.
  • 4279df6: files-sdk/nitro now serves requests Nitro handles in-process, such as the edge preset’s localFetch and an SSR $fetch to your own API. They used to fail with a 500, because the mock request Nitro builds has no socket and keeps its body on the event. The binding now uses the event’s Web Request when h3 provides one, and otherwise reads the body where h3 stores it.
  • 4279df6: files-sdk/nitro no longer adds a socket close listener per request that is never removed. On a keep-alive connection they piled up until Node printed MaxListenersExceededWarning. The listener is now removed once the response finishes.
  • aefa047: files-sdk/onedrive and files-sdk/sharepoint now upload an empty body through a resumable (control) upload with the simple PUT /content and drop the unused upload session. They used to send the invalid chunk header Content-Range: bytes 0--1/0 to a Graph upload session, which needs at least one byte.
  • aefa047: files-sdk/onedrive now reads the ONEDRIVE_DRIVE_ID / ONEDRIVE_SITE_ID / ONEDRIVE_USER_ID env targets only when no driveId, siteId, or userId option is passed. An explicit target plus an unrelated env target used to throw “pass at most one”, and since files-sdk/sharepoint always passes the resolved driveId, any of those env vars broke every SharePoint adapter.
  • aefa047: files-sdk/onedrive and files-sdk/sharepoint now list the folder a prefix points into. list() only ever read the root folder and compared the whole prefix against child names, so list({ prefix: "photos/", delimiter: "/" }) came back empty, the folder prefixes it returned led nowhere, and a Files client prefix listed nothing. The part of the prefix up to its last / now picks the folder, the rest filters child names, and keys come back in full (photos/cover.jpg); a prefix into a missing folder lists nothing.
  • add46b5: The uploadFile tool from files-sdk/openai’s createAgentsFileTools() (and agentsUploadFile()) now has a strict-mode-valid parameter schema. The OpenAI Agents SDK sends Zod-typed tools with strict: true, and the free-form metadata map could not be expressed in strict mode, so OpenAI rejected the tool definition. The Agents uploadFile tool no longer takes metadata, matching the Responses factory under strict.
  • 33891e7: The files-sdk/ovhcloud region and endpoint JSDoc now matches OVHcloud’s endpoint list. Sydney is ap-southeast-syd (not syd), and the default s3.<region>.io.cloud.ovh.net host is OVHcloud’s main endpoint, which serves every storage class and stores objects as Standard by default. It was described as the High Performance endpoint, with s3.<region>.cloud.ovh.net as Standard. The legacy perf endpoint and the Swift-backed endpoint are now described as opt-in overrides.
  • 3963e72: The published files-sdk package now ships its MIT LICENSE file (earlier tarballs had none) and declares "engines": { "node": ">=20" }, the minimum Node version the SDK runs on (it relies on Array.prototype.toSorted and toReversed). The package also gains npm keywords.
  • 82d82e0: files-sdk/pocketbase now logs in again once the superuser token from adminEmail/adminPassword expires. The first login’s promise was kept forever, so after expiry every call ran unauthenticated.
  • 82d82e0: files-sdk/pocketbase download() now reports the file’s content type from the file response instead of always application/octet-stream.
  • 82d82e0: files-sdk/pocketbase list({ prefix }) now returns only keys that start with the prefix exactly. PocketBase runs the ~ filter as SQL LIKE, so _ and % in the prefix acted as wildcards and letters matched case-insensitively (prefix: "A_" returned a1.txt). The server filter still narrows the page, and the adapter now matches the prefix exactly on the results, so a page can hold fewer than limit items.
  • 8bc864a: The files-sdk/providers catalog now matches what each adapter actually requires. Akamai and IBM Cloud Object Storage list region instead of endpoint (the endpoint is derived from it), Oracle Cloud lists its required namespace, PocketBase lists its required collection, BUNNY_STORAGE_REGION moves to required, and FIREBASE_STORAGE_BUCKET moves to optional because the bucket defaults to <projectId>.firebasestorage.app. The s3 entry now declares the AWS_ENDPOINT_URL_S3 / AWS_ENDPOINT_URL and AWS_REQUEST_CHECKSUM_CALCULATION / AWS_RESPONSE_CHECKSUM_VALIDATION variables the adapter checks, bun-s3 lists Bun’s AWS_ENDPOINT / S3_ENDPOINT, and the FTP, SFTP and WebDAV notes no longer claim signedUploadUrl() works with publicBaseUrl (it is unsupported) or that WebDAV runs on edge runtimes (it imports node:stream). Descriptions also now match current behaviour: Firebase Storage’s GOOGLE_APPLICATION_CREDENTIALS accepts any Application Default Credentials file, s3-fetch reads AWS_SESSION_TOKEN only when the keys also come from the environment, WebDAV infers token auth from a token option, and OVHcloud is no longer described as the High Performance tier.
  • e955340: files-sdk/r2 now maps Workers binding error codes to the right FilesError codes, following Cloudflare’s published table. Bad credentials (10002) and other auth failures (10003, 10018, 10035) are Unauthorized, a missing key, bucket, or upload (10007, 10006, 10024) is NotFound, and a failed precondition or non-empty bucket (10031, 10008) is Conflict. Before, 10002 mapped to NotFound, so exists() returned false on bad credentials, and 10007 (NoSuchKey) mapped to Conflict.
  • e955340: The files-sdk/r2 binding option now documents that an upload through a Workers binding needs a known length. Strings, bytes, and Blobs always work; a ReadableStream works only as a request or response body with a Content-Length or as the readable side of a FixedLengthStream, because workerd rejects a stream of unknown length.
  • e955340: In HTTP mode, files-sdk/r2’s signedUploadUrl({ maxSize }) now returns a rejected promise instead of throwing synchronously, matching binding mode and every other adapter method. This only changes direct adapter calls; files.signedUploadUrl() already rejected.
  • ac7103b: A paused resumable upload (upload(key, body, { control }) after control.pause()) now rejects when the caller’s signal (or the constructor signal) aborts. Previously the parked upload ignored the signal and the upload() promise stayed pending until resume() or control.abort(). Like any external abort, the session is kept so the upload can still be resumed from control.toJSON(), and the pause gate no longer leaves an abort listener on the signal for every pause/resume cycle.
  • ac7103b: Resumable uploads on files-sdk/gcs, files-sdk/firebase-storage, and files-sdk/google-drive now classify a failed session request by its HTTP status: 404 and 410 (an unknown or expired session) map to NotFound, 401/403 to Unauthorized, and 409/412 to Conflict, all flagged permanent. They used to surface as Provider errors, so each chunk against a dead session was retried until the retry budget ran out.
  • e955340: files-sdk/s3 with an explicit endpoint (and every S3-compatible wrapper built on it) now sets requestChecksumCalculation and responseChecksumValidation to "WHEN_REQUIRED". @aws-sdk/client-s3 3.729 and later add an x-amz-checksum-crc32 header to every PutObject and UploadPart, plus a checksum parameter to presigned PUT URLs, and some S3-compatible services reject it (Backblaze B2 reported “Unsupported header ‘x-amz-checksum-crc32’”). Checksums are now sent only where an operation requires one; DeleteObjects still carries one because S3 requires it. The AWS_REQUEST_CHECKSUM_CALCULATION / AWS_RESPONSE_CHECKSUM_VALIDATION env vars still override, and canonical AWS keeps the SDK default.
  • e955340: files-sdk/s3-fetch no longer pairs an AWS_SESSION_TOKEN from the environment with keys you pass explicitly. On a Lambda or SSO shell, s3Fetch() with static R2 or MinIO keys signed every request with the shell’s unrelated token and got a 403. The env token is now read only when accessKeyId and secretAccessKey also come from the environment; an explicit sessionToken still applies.
  • e955340: Doc-comment and message fixes in files-sdk/s3. The publicBaseUrl JSDoc now says keys are URL-encoded per segment (it wrongly said they were embedded literally), the mapS3Error and internal defaultProviderMessage notes describe how the S3-compatible wrappers actually relabel errors, and the missing-@aws-sdk/lib-storage error now says it’s also needed for multipart and for ReadableStream bodies of unknown length, not only onProgress.
  • e955340: Presigned URLs on the S3 family now reject an expiresIn over 604800 seconds (7 days, the SigV4 limit) with a clear Provider error on both engines. The fetch engine (files-sdk/s3-fetch, and the client: "fetch" mode of files-sdk/r2, files-sdk/minio, and files-sdk/rustfs) used to return a URL the server rejected, and the aws-sdk engine threw a bare “S3 error”. files-sdk/s3, files-sdk/s3-fetch, every S3-compatible wrapper, and R2 hybrid signing now declare capabilities.signedUrl.maxExpiresIn: 604800, so the useFiles gateway clamps expiry to it.
  • e955340: Resumable (control) uploads on files-sdk/s3 and its S3-compatible wrappers now grow the part size to fit the body in S3’s 10,000-part limit, up to the 5 GiB part maximum. At the 5 MiB default, bodies over about 48.8 GiB needed more than 10,000 parts, and S3 rejected part 10,001 after everything before it had uploaded. The chosen part size is pinned in the resume token.
  • 244926c: files-sdk/sftp now maps connect failures like every other error, so a rejected login is Unauthorized instead of a retryable Provider error that the client kept retrying.
  • 244926c: files-sdk/sftp list() no longer comes back empty when a subdirectory disappears partway through the recursive walk: that directory is skipped and the rest is listed, while a missing root still lists as empty.
  • 244926c: files-sdk/sftp resumable uploads (upload({ control })) now append to a <key>.fls-part staging file and rename it over the key when the upload completes. They used to write straight to the key: starting an upload deleted the existing file, a paused or crashed upload left a truncated file readable under the key, and abort() deleted the key. list() hides staging files, and writes to keys ending in .fls-part now throw.
  • aefa047: files-sdk/sharepoint now classifies Graph errors from its site and drive lookups like every other Graph error: 401/403 map to Unauthorized and 404 to NotFound. They surfaced as retryable Provider errors before.
  • df8e277: The files-sdk/soft-delete docs now describe what is trashed accurately: only deletes made through the Files instance. Overwrites (including presigned uploads) and deletes made directly against the provider are not; use versioning() to keep overwritten bytes.
  • 6e9580b: files-sdk/supabase now sends cacheControl the way Supabase expects it, as a number of seconds. Previously a header like "public, max-age=60" was stored as max-age=public, max-age=60. A max-age=<n> value (optionally with public) or a bare number of seconds is accepted; values Supabase can’t store, such as no-store or immutable, now throw instead of being mangled.
  • 6e9580b: files-sdk/supabase now recognizes a missing object. Supabase Storage answers with HTTP 400 and puts the real status in the body (statusCode: "404", code: "NoSuchKey"), which used to map to a Provider error, so exists() threw instead of returning false and head()/download() never raised NotFound. The body status and code now take priority, and a malformed-key InvalidKey error is no longer reported as Unauthorized.
  • 6e9580b: Resumable uploads (upload({ control })) on files-sdk/supabase now carry cacheControl and user metadata into the TUS session instead of dropping them. Chunks also stay at the 6 MiB size Supabase requires, so a multipart.partSize no longer breaks the upload.
  • 6e9580b: signedUploadUrl({ contentType }) on files-sdk/supabase now throws. Supabase signed upload URLs don’t bind a Content-Type, so the header it returned was advisory only. Restrict types with the bucket’s allowed MIME types, or validate at your gateway.
  • 4313433: files-sdk/svelte useList, useFile and useSearch now abort their in-flight request when the last subscriber to their stores goes away. That happens when the component is destroyed or a $: block swaps in a new query, which matches how the React and Vue bindings abort on unmount. A synchronous read such as get(store) doesn’t trigger it. A query aborted this way runs again when something subscribes to it.
  • 5981d9d: files.tier() from files-sdk/tiering now throws ReadOnly on a read-only instance (files.readonly() or readonly: true) instead of moving and deleting data. Files exposes a new isReadOnly getter so plugins whose extend methods write through an internal instance can refuse the same way. The tier() docs now note that its moves bypass hooks and the instance’s other plugins.
  • 5981d9d: With fallback: true, url() on files-sdk/tiering now checks which tier holds the key and signs against that one. Presigning adapters mint a URL without checking the object exists, so a size-routed object or one moved with tier() got a dead link to the other tier. The docs now also describe the merged list() as globally key-ordered across pages.
  • df8e277: The files-sdk/tracing docs no longer claim that inner plugins’ sub-operations become child spans. With tracing() first, each span covers the full caller-facing operation including time spent in inner plugins, but their own sub-operations call inward past it and are not traced separately; place tracing() last to get one span per provider call.
  • ac7103b: transfer() and sync() from files-sdk now drop user metadata when the destination can’t store it (dest.capabilities.metadata is false), as their docs describe. Before, every metadata-bearing object failed with a “metadata is not supported” per-key error when copying to an adapter such as WebDAV, Dropbox, or Bunny Storage.
  • ac7103b: A throwing onProgress callback no longer breaks transfer() or sync() from files-sdk. Progress is now fire-and-forget, as it is for uploads: before, a throw turned every key that had already been copied into a per-key error, and sync({ prune: true }) rejected after it had already deleted destination keys.
  • 24a0f92: files-sdk/uploadthing’s signedUploadUrl() now throws on a positive minSize instead of silently ignoring it, because UploadThing’s ingest URLs have no minimum-size constraint. minSize: 0 is still accepted.
  • 8dcd641: The files-sdk/validation and files-sdk/content-type docs now spell out plugin order. contentType() must come before validation() in the plugins array: in the reverse order validation() approves the type the client claimed (say image/png from avatar.png) and contentType() then relabels the upload from its bytes (to text/html), slipping past allowedTypes. validation() must also come before versioning(), softDelete() and dedup(), whose internal writes (.versions/…, .trash/…, .dedup/…, empty pointer bodies) would otherwise be rejected by key or minSize rules.
  • 8dcd641: validation() (files-sdk/validation) no longer reads a known-length body into memory to check maxSize / minSize. Strings, byte arrays, Blobs and Files are measured from their length and forwarded untouched, so a multi-gigabyte File over the limit is rejected without being buffered, and a size-only policy no longer overrides the adapter’s default content type. Only unknown-length streams (and a Blob that reports no finite size) are still buffered to measure them.
  • 8dcd641: validation() (files-sdk/validation) now stores the type it approved. With allowedTypes set, the checked type (an explicit contentType, else the Blob’s type, else the type implied by the key’s extension) is forwarded as the upload’s contentType. Previously an upload approved as image/png from its .png key was stored as the adapter’s default, and with a size rule a string body was forced to text/plain.
  • a6ec2bd: files-sdk/vercel-blob now throws when upload({ cacheControl }) has no max-age directive (for example "no-store"). Vercel Blob only stores a cache max-age, so such values used to be silently dropped; pass a value like "public, max-age=3600" instead.
  • a6ec2bd: files-sdk/vercel-blob resumable and multipart uploads (control, multipart) now send the adapter’s allowOverwrite setting and the upload’s cacheControl, like a plain upload() does. Previously a resumable upload to an existing key failed under the default allowOverwrite: true, and its cacheControl was silently dropped.
  • a6ec2bd: files-sdk/vercel-blob now classifies @vercel/blob errors by their class. The SDK’s errors carry no HTTP status and a generic name, so every failure used to surface as a Provider error: exists() on a missing key threw instead of returning false, and head()/download() never reported NotFound. Missing blobs now map to NotFound, access and token errors to Unauthorized, ETag precondition failures and uploads to an existing key under allowOverwrite: false to Conflict, and deterministic rejections (content type not allowed, file too large, missing store) are flagged permanent so retries doesn’t re-send them.
  • df8e277: versioning() (files-sdk/versioning) no longer snapshots on a copy or move of a key onto itself. Those change nothing, but each one used to add a version and, with limit, prune an older one, so a no-op could destroy history.
  • df8e277: versioning() (files-sdk/versioning) now orders versions by when each snapshot was taken, not by the snapshotted object’s last-modified time. A native move (fs, memory, FTP, SFTP) carries the source’s older time onto the destination, so restoreVersion() could undo the wrong change and limit could prune the snapshot it had just taken. New version ids are <taken>-<modified>-<etag>; ids written by earlier releases still parse and sort before newer ones, and FileVersion.lastModified still reports the object’s own last-modified time.
  • 4313433: files-sdk/vue and files-sdk/svelte now mirror failures from versions, restoreVersion, trashed, restoreTrashed and purge into error, as files-sdk/react already did. All three bindings now also record errors thrown while iterating listAll() or search().
  • 4313433: files-sdk/vue and files-sdk/svelte now re-export the FileVersion, TrashedFile and NativeFileRef types, and files-sdk/react adds NativeFileRef, so all three bindings expose the same client types. The new UploadManyCallOptions and UploadProgressCallback types are exported from each binding as well.
  • 33891e7: The files-sdk/vultr region docs and missing-region error now use Vultr’s real cluster codes (ewr1, sjc1, ams1, blr1, del1, sgp1). The old examples (ewr, sjc, ams, …) produced hosts like ewr.vultrobjects.com that don’t exist, and lux isn’t a Vultr cluster.
  • 244926c: files-sdk/webdav list() no longer comes back empty when a subcollection disappears partway through the recursive walk: that collection is skipped and the rest is listed, while a missing root still lists as empty.
  • 244926c: files-sdk/webdav now uses token auth when a token is passed without authType, as documented. The webdav client inferred no auth in that case, so requests went out without an Authorization header.
  • ac7103b: files.zip(selection) from files-sdk/zip no longer starts listing or downloading until the returned stream is first read, as documented. The stream used to pull one chunk eagerly, so creating an archive stream you never consumed still listed the selection and opened the first download. The unzip maxEntries, maxEntrySize, and maxTotalSize options are now documented in the type definitions.

Last updated on September 29, 2026

Was this page helpful?