---
title: S3 (fetch)
description: 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](https://github.com/mhart/aws4fetch) (~2.5&nbsp;KB gzipped, Web Crypto only), so nothing from `@aws-sdk/*` is installed or bundled:

```package-install
files-sdk
```

## When to use it

[`files-sdk/s3`](/docs/adapters/s3) and the S3-compatible wrappers built on it ([MinIO](/docs/adapters/minio), [Tigris](/docs/adapters/tigris), [Wasabi](/docs/adapters/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](/docs/adapters/r2#lightweight-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

```ts lineNumbers
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:

```ts lineNumbers
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](/docs/api/download#byte-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&nbsp;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](/docs/conditional-operations).
- `files.raw` is the aws4fetch `AwsClient`, not an `S3Client`.

## Options

<AutoTypeTable
  path="../../packages/files-sdk/src/s3-fetch/index.ts"
  name="S3FetchAdapterOptions"
/>

## 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. |
