---
title: Amazon S3
description: AWS S3 (and any S3-compatible bucket). Uses the standard AWS credential chain - environment, IAM role, shared profile.
related:
  - /guides/presigned-upload-validation
  - /guides/resume-s3-multipart-upload
  - /guides/private-video-range-streaming
  - /guides/migrate-s3-to-r2
  - /guides/express-s3-file-upload
  - /guides/s3-conditional-upload-conflicts
---

## Installation

`@aws-sdk/client-s3`, `@aws-sdk/s3-presigned-post`, and `@aws-sdk/s3-request-presigner` are optional peer dependencies of `files-sdk` - install alongside the SDK so the adapter's imports resolve at runtime.

```package-install
files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
```

`@aws-sdk/lib-storage` is optional too. Install it to report true byte-level [upload progress](/docs/api/upload#progress-tracking) via `onProgress`, to use `multipart`, or to upload a `ReadableStream` body of unknown length. It's only loaded when `upload` uses `onProgress`, `multipart`, or a `ReadableStream` body of unknown length.

On Cloudflare Workers and other edge runtimes without a `DOMParser`, `@aws-sdk/client-s3`'s XML parsing fails at runtime. Use [`s3Fetch()` from `files-sdk/s3-fetch`](/docs/adapters/s3-fetch) there instead — the same S3 protocol over a SigV4-signed `fetch`, with no `@aws-sdk/*` packages.

## Usage

```ts lineNumbers
import { Files } from "files-sdk";
import { s3 } from "files-sdk/s3";

const files = new Files({
  adapter: s3({
    bucket: "uploads",
    region: "us-east-1",
    // credentials auto-loaded from the AWS chain
    // (env vars, IAM role, shared profile, ...)
  }),
});
```

Canonical AWS S3 buckets support provider-native [conditional create, replace, exact read, delete, and copy](/docs/conditional-operations). Conditional copy sends its source ETag and destination create/replace predicate in one `CopyObject` request.

The primitives are exposed only when the client will talk to AWS: no `endpoint` option and no `AWS_ENDPOINT_URL_S3` / `AWS_ENDPOINT_URL` redirect in the environment (the AWS SDK honors those on its own), or one whose hostname is under `amazonaws.com` (or `amazonaws.com.cn`), such as an explicit regional, VPC, FIPS, or dual-stack endpoint. The same check decides whether the adapter declares S3 event records for [`files-sdk/events`](/docs/events) (`capabilities.events`). A shared-config `endpoint_url` — profile-level or under a `services` section — is invisible at construction, so it is caught at request time instead: a conditional request whose resolved hostname is not under `amazonaws.com` (or `amazonaws.com.cn` in the China regions) fails closed before it is sent. S3-compatible services differ in conditional-header support, so in every one of those cases the adapter refuses rather than risk an unconditional overwrite. AWS-hosted endpoints (VPC, FIPS, dual-stack, GovCloud) resolve under `amazonaws.com` and need no override. Pass `conditional: true` to opt an S3-compatible endpoint you have verified back in (it skips both checks), or `conditional: false` to disable the primitives on a canonical bucket.

Conditional copy needs `@aws-sdk/client-s3` 3.919.0 or newer, where `CopyObject` gained `IfMatch` / `IfNoneMatch` (the peer range now starts at 3.1079.0). Because an optional peer range is advisory, the adapter also verifies at request time that every predicate it set was actually serialized as a header and rejects the call if the installed client dropped one. Conditional uploads take a buffered body (`Blob`, `Uint8Array`, `ArrayBuffer`, or string) — a stream is rejected before I/O.

## Options

<AutoTypeTable
  path="../../packages/files-sdk/src/s3/core.ts"
  name="S3AdapterOptions"
/>

`defaultProviderMessage` is internal: the S3-compatible wrappers set it so their errors read "Wasabi error" rather than "S3 error". You don't need to pass it.

## Limits

These come from S3 itself, so they apply to [`s3Fetch()`](/docs/adapters/s3-fetch) and every S3-compatible wrapper as well:

- **Presigned URLs live at most 7 days.** SigV4 caps a presigned URL at 604800 seconds, so `url()` and `signedUploadUrl()` (a presigned PUT, or with `maxSize` a presigned POST) throw an `Invalid` error for a longer `expiresIn` (or `defaultUrlExpiresIn`) instead of returning a URL the server would reject. The lifetime must also be a whole number of seconds, at least 1; a zero, negative, or fractional one throws `Invalid` too. [`capabilities.signedUrl.maxExpiresIn`](/docs/capabilities) reports the ceiling. With a `publicBaseUrl`, a plain `url(key)` returns the permanent link, so the cap doesn't apply to it; `url(key, { expiresIn })` still presigns and is capped.
- **`copy()` and `move()` switch to a multipart copy past 5 GiB.** A copy is one server-side `CopyObject` request, which S3 limits to 5 GiB sources. When S3 refuses a larger source, the adapter (on both engines) copies it server-side with `UploadPartCopy` in 512 MiB parts instead, carrying the source's `Content-Type`, `Cache-Control`, `Content-Disposition`, `Content-Encoding`, `Content-Language`, `Expires`, and user metadata. Object tags aren't carried. On AWS every part is pinned to the source's ETag, so a source overwritten mid-copy fails with `Conflict`. A [conditional copy](/docs/conditional-operations#conditional-copy) stays a single request, so an oversized source throws `Unsupported`.
- **User metadata travels as `x-amz-meta-*` headers.** Keys must be HTTP tokens (letters, digits, and the punctuation HTTP allows in a header name), and values printable ASCII without control characters. Latin-1 text such as `café` is refused too: the HTTP client sends `é` as one byte while SigV4 signs it as two, so S3 would reject the request as a signature mismatch. Anything else throws `Invalid` before any request is sent; encode such values first (for example with `encodeURIComponent`). The same rule applies to `contentType` and `cacheControl`, which travel as the `Content-Type` and `Cache-Control` headers.
- **Multipart and resumable (`control`) uploads fit S3's part limits.** Every part but the last must be at least 5 MiB, and an upload holds at most 10,000 parts of at most 5 GiB each. The adapter raises a smaller `multipart.partSize` to 5 MiB and, when the body's length is known, raises it further until the body fits in 10,000 parts. An unknown-length stream can't be fitted, so give it a `partSize` big enough on its own. A resumable upload pins the chosen size in its resume token.

### S3-compatible endpoints and checksums

Recent `@aws-sdk/client-s3` versions add an `x-amz-checksum-crc32` header to every `PutObject` and `UploadPart`, and some S3-compatible services reject it. When you pass an `endpoint`, the adapter sets `requestChecksumCalculation` and `responseChecksumValidation` to `"WHEN_REQUIRED"`, so checksums are only sent and checked where an operation requires one. `DeleteObjects` still carries one, because S3 requires it. Set `AWS_REQUEST_CHECKSUM_CALCULATION` / `AWS_RESPONSE_CHECKSUM_VALIDATION` to override. Canonical AWS (no `endpoint`) keeps the SDK default. Presigned PUT URLs from `signedUploadUrl()` never carry a precomputed checksum, on any endpoint: the SDK would sign the empty body's CRC32 into the URL, and S3 would reject every real upload through it.

## Compatibility

| Method | Status | Notes |
| --- | :-: | --- |
| `upload` | ✅ |  |
| `download` | ✅ |  |
| `delete` | ✅ |  |
| `list` | ⚠️ | The S3 list API returns no per-object `Content-Type`, so `contentType` is inferred from the key's extension (`application/octet-stream` when unknown). Use `head()` for the stored value. |
| `search` | ✅ |  |
| `head` | ✅ |  |
| `exists` | ✅ |  |
| `copy` | ✅ | Server-side `CopyObject`, or a server-side multipart copy for sources over 5 GiB (see [Limits](#limits)). |
| `url` | ✅ | Presigned URLs are capped at 7 days (see [Limits](#limits)). |
| `signedUploadUrl` | ✅ | A positive `minSize` needs `maxSize`, which selects the presigned POST policy that enforces both; on its own it throws `Unsupported`. |
| conditional operations | ✅ | Single-object native requests. Bulk, multipart, resumable, and custom-endpoint conditionals are unsupported. |
