patterntypescriptModerate
AWS S3 Presigned URL File Upload
Viewed 0 times
@aws-sdk/client-s3 v3, @aws-sdk/s3-request-presigner v3
s3presigned urlfile uploadPutObjectCommandaws sdk v3direct uploadcors s3
Error Messages
Problem
Uploading files to S3 through your server wastes bandwidth, adds latency, and increases cost. Giving clients direct S3 access requires exposing AWS credentials.
Solution
Generate a short-lived presigned PUT URL server-side using the AWS SDK. Return the URL to the client. The client uploads directly to S3 using a plain HTTP PUT — no AWS credentials are exposed. Validate the upload server-side afterward if needed.
Why
Presigned URLs are signed with your AWS credentials server-side and grant time-limited permission for a specific operation on a specific key. The client performs the upload without ever seeing AWS credentials.
Gotchas
- Set an appropriate ContentType in the presigned URL command — S3 will reject uploads where Content-Type doesn't match
- The bucket must NOT be publicly accessible; only the specific key is temporarily accessible via the presigned URL
- Presigned URLs expose the bucket name and key structure — consider randomizing keys to prevent enumeration
- CORS must be configured on the S3 bucket to allow PUT requests from your frontend domain
Code Snippets
Generate a presigned S3 PUT URL (server-side)
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({ region: process.env.AWS_REGION! });
export async function getUploadUrl(filename: string, contentType: string) {
const key = `uploads/${crypto.randomUUID()}/${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: key,
ContentType: contentType,
});
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); // 5 minutes
return { uploadUrl, key };
}
// Client-side usage:
// const { uploadUrl, key } = await fetch('/api/upload-url').then(r => r.json());
// await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } });Revisions (0)
No revisions yet.