# Storage Providers

upup uploads to any **S3-compatible** object store. One uploader UI, one config
shape — the only thing that changes between AWS S3, Cloudflare R2, MinIO,
Backblaze B2, and the rest is the `storage` block you hand the server (or the
presign endpoint you point the client at).

There are two ways to wire storage, and in both the browser uploads bytes
directly to storage using short-lived presigned URLs — the storage credentials
never leave your server. In **client mode** your own presign endpoint signs
those URLs, and upup never sees the credentials. In **server mode**
[`@useupup/server`](/docs/guides/server-mode-setup/) holds the credentials,
signs the URLs for you, and writes cloud-drive files to storage itself. Which
one to pick, and what changes on the wire, is covered in
[Client Mode vs Server Mode](/docs/guides/modes/).

This page focuses on server mode, where the storage provider is a
`createUpupHandler` config value. Cloud-drive sources land in that same storage:
after the user consents, the server exchanges the OAuth token and streams the
picked file straight into your bucket — the bytes never pass through the browser.

<DriveOAuthDiagram />

## Pick your provider

Each guide below carries the full setup for one provider: the `storage` block,
where to create the bucket and keys in that provider's console, the CORS step,
and the mistakes that provider specifically invites.

- [Amazon S3](/docs/guides/storage/aws-s3/) — the native AWS path, including IAM
  roles instead of static keys.
- [Cloudflare R2](/docs/guides/storage/cloudflare-r2/) — account-scoped endpoint,
  `region: 'auto'`, zero egress fees.
- [Backblaze B2](/docs/guides/storage/backblaze-b2/) — application keys and the
  region baked into the endpoint host.
- [DigitalOcean Spaces](/docs/guides/storage/digitalocean-spaces/) — Spaces keys
  and the datacenter-scoped endpoint.
- [MinIO](/docs/guides/storage/minio/) — self-hosted S3, plus the local Docker
  setup upup's own e2e suite uses.
- [Azure Blob Storage](/docs/guides/storage/azure-blob/) — **not** S3-compatible;
  `createUpupHandler` rejects it, so use the client-mode SAS path.
- [Other S3-compatible services](/docs/guides/storage/s3-compatible/) — Wasabi,
  Google Cloud Storage, Supabase, Hetzner, Scaleway, Storj, and every other
  store that speaks the S3 API, including ones upup has never heard of.

## Supported providers

`@useupup/core` exports a `StorageProvider` enum with the values below. Every one
except `azure` exposes an S3-compatible API and is served by `@useupup/server`;
`azure` is the sole exception.

| `storage.type` | Service                           | S3 endpoint pattern                                                      | Notes                                                                                     |
| -------------- | --------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `aws`          | Amazon S3                         | none — omit `endpoint`                                                   | The only provider that needs no endpoint. [Guide](/docs/guides/storage/aws-s3/)           |
| `r2`           | Cloudflare R2                     | `https://<account-id>.r2.cloudflarestorage.com`                          | `region` is the literal `auto`. [Guide](/docs/guides/storage/cloudflare-r2/)              |
| `minio`        | MinIO                             | your own server URL                                                      | Self-hosted; requires path-style. [Guide](/docs/guides/storage/minio/)                    |
| `backblaze`    | Backblaze B2                      | `https://s3.<region>.backblazeb2.com`                                    | Region looks like `us-west-004`. [Guide](/docs/guides/storage/backblaze-b2/)              |
| `digitalocean` | DigitalOcean Spaces               | `https://<region>.digitaloceanspaces.com`                                | Region is the datacenter, e.g. `nyc3`. [Guide](/docs/guides/storage/digitalocean-spaces/) |
| `wasabi`       | Wasabi                            | `https://s3.<region>.wasabisys.com`                                      | [Guide](/docs/guides/storage/s3-compatible/)                                              |
| `gcs`          | Google Cloud Storage (S3 interop) | `https://storage.googleapis.com`                                         | One global host; needs HMAC keys. [Guide](/docs/guides/storage/s3-compatible/)            |
| `supabase`     | Supabase Storage                  | `https://<project-ref>.storage.supabase.co/storage/v1/s3`                | Path ends at `/s3`. [Guide](/docs/guides/storage/s3-compatible/)                          |
| `hetzner`      | Hetzner Object Storage            | `https://<location>.your-objectstorage.com`                              | Location is `fsn1`, `nbg1`, or `hel1`. [Guide](/docs/guides/storage/s3-compatible/)       |
| `scaleway`     | Scaleway Object Storage           | `https://s3.<region>.scw.cloud`                                          | Regions incl. `fr-par`, `nl-ams`. [Guide](/docs/guides/storage/s3-compatible/)            |
| `linode`       | Akamai (Linode) Object Storage    | `https://<region>.linodeobjects.com`                                     | Host format changed in 2025 — copy it from the dashboard.                                 |
| `vultr`        | Vultr Object Storage              | `https://<region>.vultrobjects.com`                                      | Region is the datacenter, e.g. `ewr1`.                                                    |
| `upcloud`      | UpCloud Object Storage            | `https://<instance-id>.upcloudobjects.com`                               | Endpoint is per-instance — copy it from the console.                                      |
| `ovhcloud`     | OVHcloud Object Storage           | `https://s3.<region>.io.cloud.ovh.net`                                   | Regions incl. `gra`, `rbx`, `sgp`.                                                        |
| `alibaba`      | Alibaba Cloud OSS                 | `https://oss-<region>.aliyuncs.com`                                      | OSS host, no `s3.` prefix. Rejects path-style.                                            |
| `oracle`       | Oracle Cloud Object Storage       | `https://<namespace>.compat.objectstorage.<region>.oci.customer-oci.com` | Sign with a Customer Secret Key, not your native OCI key.                                 |
| `contabo`      | Contabo Object Storage            | `https://<region>.contabostorage.com`                                    | Regions incl. `eu2`, `usc1`, `sin1`. Ceph-based, partial S3 coverage.                     |
| `storj`        | Storj                             | `https://gateway.storjshare.io`                                          | One global gateway; `region` is `global-1` on new projects.                               |
| `idrive`       | IDrive e2                         | `https://s3.<region>.idrivee2.com`                                       | Host is provisioned per account — copy it from the dashboard.                             |
| `ceph`         | Ceph (RADOS Gateway)              | your own RADOS Gateway URL                                               | Self-hosted.                                                                              |
| `azure`        | Azure Blob Storage                | — (no S3 API)                                                            | Not servable — throws at construct time. [Guide](/docs/guides/storage/azure-blob/)        |

Placeholders in angle brackets come from your provider's console. Where a pattern
is marked "copy it from the dashboard", the host is provisioned per account or
per instance and cannot be derived from your region — read the exact value off
the console rather than assembling it.

The `type` value is a label. `@useupup/server` builds the same AWS-SDK S3 client
for every S3-compatible value and reaches your backend through `endpoint` — it
does not branch on `type`. Because `storage.type` also accepts any string, a
store that isn't in the enum works too, as long as it speaks the S3 API: use the
[generic recipe](/docs/guides/storage/s3-compatible/).

`azure` is the one value with no S3 surface. `createUpupHandler` throws an
`UpupConfigError` at construct time if you pass it, rather than failing later at
request time. See [Azure Blob Storage](/docs/guides/storage/azure-blob/).

## The `storage` config

Every provider guide fills in the same object:

| Field             | Type                        | Required        | Notes                                                                                     |
| ----------------- | --------------------------- | --------------- | ----------------------------------------------------------------------------------------- |
| `type`            | `StorageProvider \| string` | Yes             | Provider label from the table above. Also accepts any string for stores not listed.       |
| `bucket`          | `string`                    | Yes             | The bucket (or "Space" / "container") name.                                               |
| `region`          | `string`                    | Yes             | Must match the bucket's region. Use `auto` for Cloudflare R2.                             |
| `accessKeyId`     | `string`                    | Both or neither | Omit both to use the host's IAM role / instance profile (AWS). A half-set pair throws.    |
| `secretAccessKey` | `string`                    | Both or neither | Pairs with `accessKeyId`.                                                                 |
| `endpoint`        | `string`                    | Non-AWS only    | The provider's S3 endpoint URL. Omit for native AWS S3.                                   |
| `forcePathStyle`  | `boolean`                   | Optional        | Defaults to `true` when `endpoint` is set (MinIO requires it); ignored for native AWS S3. |

`bucket`, `region`, and — when set — a complete `accessKeyId`/`secretAccessKey`
pair are validated at construct time, so a forgotten env var fails loudly on boot
instead of surfacing as a confusing 500 later.

The [AWS S3 guide](/docs/guides/storage/aws-s3/) shows the complete
`createUpupHandler` call; every other guide shows only the `storage` block that
changes, since `uploadTokenSecret` and the rest of the handler are identical. See
the [Server Mode setup guide](/docs/guides/server-mode-setup/) for the full
walkthrough (`getUserId`, `providers`, `tokenStore`, and tuning).

<Callout type="warning" title="Never hard-code credentials">
    Placeholders in every guide are environment variables. Access keys and
    secrets belong in server-side environment variables, never in a file you
    commit and never in anything the browser can fetch. In client mode the
    browser sees only the short-lived signed URL your endpoint returns.
</Callout>

## Troubleshooting

**`SignatureDoesNotMatch` / 403 on upload.** Almost always a wrong `region` or a
skewed server clock. Make `region` match the bucket's actual region (use `auto`
for R2), and confirm the server clock is accurate — S3 signatures are
time-sensitive and tolerate only a few minutes of drift. A half-set credential
pair (one of `accessKeyId`/`secretAccessKey` blank, the classic
`process.env.X!` → `""` bug) throws at construct time; set both or neither.

**Path-style vs virtual-hosted addressing.** upup enables path-style
automatically whenever `endpoint` is set, which is what MinIO and most
S3-compatible stores expect. If your provider only supports virtual-hosted-style
and you see hostname or TLS errors, set `forcePathStyle: false`. This flag is
ignored for native AWS S3.

**Bucket or endpoint errors (`NoSuchBucket`, DNS/TLS failures).** Check that
`bucket` exists in that `region`, and that the region segment inside `endpoint`
matches `region`.

**CORS.** The browser uploads directly to storage by default in both client
and server mode — server mode's presign routes hand it a signed URL rather than
proxying the bytes — so the bucket must allow your app's origin for the signed
`PUT` (and its headers). The exceptions are cloud-drive transfers (server-to-S3)
and a deployment using `onPresignResponse` to rewrite the URL to a same-origin
proxy. See [Credentials And CORS](/docs/credentials-configuration/).

## Next steps

- [Server Mode — Setup](/docs/guides/server-mode-setup/) — the full `createUpupHandler`
  walkthrough: OAuth, token store, tuning, and re-auth.
- [Quickstarts](/docs/quickstarts/react/) — copy-paste starting points for React,
  Vue,
  Svelte, Angular, Vanilla JS, Preact, and Next.js.
