Amazon S3
Configuration, CORS, and bucket policies for AWS S3.
Amazon S3 supports presigned PUT and POST uploads, object ACLs, SHA-256 checksums, and multipart uploads.
Bucket CORS configuration
Because the browser uploads directly to your S3 bucket, configure a bucket CORS policy:
[
{
"AllowedOrigins": ["https://your-app.example", "http://localhost:3000"],
"AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Type"],
"MaxAgeSeconds": 3000
}
]Server configuration
import { S3Client } from "@aws-sdk/client-s3";
import { dimahS3, route } from "@dimah-s3/server";
export const awsS3 = new S3Client({
region: process.env.S3_REGION!,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
},
requestChecksumCalculation: "WHEN_REQUIRED",
});
export const s3 = dimahS3({
client: awsS3,
bucket: process.env.S3_BUCKET!,
routes: {
avatar: route({
upload: {
fileTypes: ["image/*"],
maxFileSize: 2 * 1024 * 1024,
},
}),
},
});AWS SDK versions from 3.729 default requestChecksumCalculation to
"WHEN_SUPPORTED". Presigned browser uploads cannot match the checksum that
mode adds, so dimah-s3 refuses to presign with such a client.
Frequently asked questions
If your bucket allows object ACLs, set upload: { acl: "public-read" } on the
route:
avatar: route({
upload: {
acl: "public-read",
fileTypes: ["image/*"],
maxFileSize: 2 * 1024 * 1024,
},
});New S3 buckets commonly use Bucket owner enforced Object Ownership, which disables ACLs. For those buckets, keep uploads private and grant public reads through a bucket policy or CDN instead.
First, make sure AllowedOrigins exactly matches the browser origin, including
the scheme and development port. Then allow the upload method and request
headers used by the presigned request. A presigned PUT sends metadata,
checksum, and tag headers, so AllowedHeaders: ["*"] is the simplest rule.
ExposeHeaders does not fix a 403, but it must include ETag when the
browser needs to read multipart responses.
A 403 with SignatureDoesNotMatch usually means the client changed or
dropped a header. A custom client must send exactly the headers from the
presign response.