Config generator

Pick S3, R2, Tigris or Cloudinary, type your bucket and site origins, and copy the .env template, CORS rules and permissions UploadCN needs.

Direct-to-bucket uploads need three things outside your code: environment variables for the upload route, a CORS rule on the bucket so browsers may upload to it, and an access key that is allowed to do it. Each provider wants them in its own format. Pick yours, fill in the bucket name and the origins your users upload from, and copy each block.

Storage provider

The sites your users upload from, one per line or separated by commas. Scheme and host only, no path.

Environment variables

.env.local
# Amazon S3. Read by the upload route: npx shadcn@latest add @uploadcn/upload-route
S3_BUCKET=my-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
# Optional: a public base URL for reading files (a CDN or public bucket)
# S3_PUBLIC_URL=
  • Leave S3_ENDPOINT unset for AWS: it is only for S3-compatible services.
  • The scaffolded route uses R2 when R2_ACCOUNT_ID is set, so keep the R2_* variables out of this file.

CORS

The browser uploads straight to the bucket, so the bucket has to allow your origins. Use either format.

1. S3 console

Open the bucket, then Permissions, then Cross-origin resource sharing (CORS), and paste this JSON.

S3 console
[
  {
    "AllowedOrigins": [
      "https://your-app.com"
    ],
    "AllowedMethods": [
      "PUT"
    ],
    "AllowedHeaders": [
      "content-type"
    ],
    "ExposeHeaders": [
      "ETag"
    ],
    "MaxAgeSeconds": 3600
  }
]

2. AWS CLI

The CLI wraps the same rules in a CORSRules object. Save it as cors.json, then run the command.

cors.json
{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://your-app.com"
      ],
      "AllowedMethods": [
        "PUT"
      ],
      "AllowedHeaders": [
        "content-type"
      ],
      "ExposeHeaders": [
        "ETag"
      ],
      "MaxAgeSeconds": 3600
    }
  ]
}
aws s3api put-bucket-cors --bucket my-bucket --cors-configuration file://cors.json

IAM policy

The least the access key behind S3_ACCESS_KEY_ID needs. Attach it to the IAM user or role that owns the key.

policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}
  • s3:PutObject also covers creating, uploading the parts of and completing multipart uploads.
  • The bucket can stay private: uploads use signed URLs, so no bucket policy or public access is needed.

Abandoned multipart uploads

Optional. Parts of uploads that are never finished are billed until removed. This lifecycle rule aborts them after 7 days.

lifecycle.json
{
  "Rules": [
    {
      "ID": "abort-incomplete-multipart-uploads",
      "Status": "Enabled",
      "Filter": {
        "Prefix": ""
      },
      "AbortIncompleteMultipartUpload": {
        "DaysAfterInitiation": 7
      }
    }
  ]
}
aws s3api put-bucket-lifecycle-configuration --bucket my-bucket --lifecycle-configuration file://lifecycle.json
  • put-bucket-lifecycle-configuration replaces the bucket's whole lifecycle configuration. If the bucket already has rules, add this one to them instead.

Official Amazon S3 docs

These formats were checked against the pages below. Providers change their consoles and APIs, so follow the links when something does not match. The full UploadCN setup is in the Amazon S3 guide.

Everything is generated in your browser as you type. Nothing you enter is sent anywhere, and the output never contains secrets: paste your keys into .env.local yourself.

The tool works with a keyboard and a screen reader: the providers are a radio group, every code block can be focused and scrolled, each copy is confirmed in words, and problems with what you typed are announced as you type.

What the output is for

BlockWhere it goes
.env.localYour app. The names are the ones the upload route reads.
CORSThe bucket, through the provider's console or CLI.
IAM policy or tokenThe access key your server signs uploads with.
Upload presetCloudinary only: the unsigned preset the browser uploads with.

Why these rules

Every block is the smallest configuration that lets UploadCN work:

  • PUT only. The browser sends one kind of request to the bucket: a PUT to a URL your server signed, for a whole file or for one part of a multipart upload. Reads, deletes and multipart bookkeeping happen on your server.
  • content-type only. It is the single header the browser sends, and the signature locks its value.
  • ETag exposed. Multipart uploads read each part's ETag response header to finish the upload. Without it, files at or above the multipart threshold (64 MiB by default) fail on their first part with a “Missing ETag” error.
  • Object permissions only. The access key needs to put, read and delete objects, and to abort and list the parts of multipart uploads. It never needs to list or manage buckets, and the bucket can stay private.

Checked against the providers' docs

The formats come from each provider's official documentation, linked under every result. They were last checked in November 2026. Two things could not be confirmed in a provider's docs and are marked as notes in the output: ExposeHeaders on Tigris (its CORS page does not show the field), and how Cloudinary treats browser origins (its docs describe no CORS setting).

After you paste

Add the route and point your components at it

npx shadcn@latest add @uploadcn/upload-route @uploadcn/upload-config

Cloudinary needs no route for unsigned uploads: use the adapter block from the generator instead.

Restart the dev server

Next.js reads .env.local at startup.

Upload a small file, then a large one

A small file checks the signature and CORS. A file over 64 MiB also checks multipart and the exposed ETag. If the browser console shows a CORS error, compare the origin in the error with the ones in your rule: scheme, host and port must match exactly.

Provider guides

On this page

Sponsor UploadCNPut your logo in front of developers choosing how their app uploads files.