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

S3 (fetch)

AWS S3 and any S3-compatible bucket over a SigV4-signed fetch engine - no @aws-sdk/* packages, built for Cloudflare Workers and other edge runtimes.

Installation

This adapter has no extra peer dependencies. It signs requests with aws4fetch (~2.5 KB gzipped, Web Crypto only), so nothing from @aws-sdk/* is installed or bundled:

npm install files-sdk
pnpm add files-sdk
yarn add files-sdk
bun add files-sdk

When to use it

files-sdk/s3 and the S3-compatible wrappers built on it (MinIO, Tigris, Wasabi, …) import @aws-sdk/client-s3. On a browser-targeted bundle that SDK’s XML parser needs a DOMParser, which Cloudflare Workers don’t provide - every list() or error-body parse throws DOMParser is not defined at runtime. s3Fetch() is the same engine that backs r2()’s fetch client, pointed at an endpoint of your choosing, for exactly those runtimes.

Outside Workers and edge runtimes prefer files-sdk/s3: it carries the full surface (multipart and resumable uploads, batched deletes, byte-level progress, the AWS credential chain) that this engine trades away for size.

Usage

import { Files } from "files-sdk";
import { s3Fetch } from "files-sdk/s3-fetch";

export default {
  async fetch(request: Request, env: Env) {
    const files = new Files({
      adapter: s3Fetch({
        bucket: "uploads",
        endpoint: "https://s3.us-east-1.amazonaws.com",
        region: "us-east-1",
        accessKeyId: env.AWS_ACCESS_KEY_ID,
        secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
      }),
    });
    // ...
  },
};

Credentials are static: pass accessKeyId + secretAccessKey (and sessionToken for temporary credentials), or set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN where a process.env exists. There is no AWS credential chain - no IAM role, shared profile, or SSO resolution - because that chain lives in the SDK this adapter avoids.

Addressing

Requests default to virtual-hosted style (https://<bucket>.<endpoint host>/<key>). Set forcePathStyle: true for services without per-bucket DNS - MinIO, LocalStack, most self-hosted gateways - and for bucket names containing dots, which break virtual-hosted TLS:

s3Fetch({
  bucket: "uploads",
  endpoint: "http://localhost:9000",
  forcePathStyle: true,
  accessKeyId: "minioadmin",
  secretAccessKey: "minioadmin",
});

Only the endpoint’s origin is used; a path component is dropped. Front the service with a hostname rather than a route prefix.

Trade-offs

Same coverage and limits as the R2 fetch client: upload, download (including ranges), head, exists, delete, list (including delimiter folding), server-side copy, presigned url(), and signedUploadUrl() are all covered. Against files-sdk/s3:

  • ReadableStream bodies are buffered in memory before a single PUT (a lone PUT needs a Content-Length, and single-request uploads cap at 5 GB). Inside a Worker that buffering counts against the isolate’s memory limit.
  • multipart and resumable (control) uploads throw instead of engaging the S3 multipart API.
  • Bulk deletes fan out as per-key delete() calls instead of batched DeleteObjects requests. On Workers each call is a subrequest, so chunk large bulk deletes to stay under the per-invocation subrequest cap.
  • signedUploadUrl() returns a presigned PUT; maxSize throws because enforcing it needs a presigned POST policy this engine doesn’t implement. Enforce upload caps at your application gateway instead.
  • Keys containing . or .. path segments are rejected - URL normalization would silently sign a request for a different key.
  • Byte-level onProgress reporting falls back to the SDK’s generic reporting.
  • No provider-native conditional operations.
  • files.raw is the aws4fetch AwsClient, not an S3Client.

Options

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

Compatibility

Method Status Notes
upload ⚠️ Single PUT - ReadableStream bodies are buffered in memory first, and multipart / resumable control uploads throw.
download
delete Bulk deletes fan out per key (no batched DeleteObjects).
list ⚠️ The S3 list API returns no per-object Content-Type, so type is inferred from the key’s extension (application/octet-stream when unknown). Use head() for the stored value.
search
head
exists
copy
url
signedUploadUrl ⚠️ PUT URL only - maxSize throws (no presigned POST policy).
conditional operations Not advertised on this engine.

Was this page helpful?