Skip to content

Presigned URLs and browser uploads

A presigned URL carries a signature made with your access key, so whoever holds it can do one thing (download or upload one object) until it expires, without seeing your key. Presigned URLs last up to 7 days (604,800 seconds).

The key that signs the URL must be allowed to do the action: a Read only key can presign downloads; uploads need Read & write. Revoking the key also stops its URLs from working.

Terminal window
aws s3 presign s3://my-app-uploads/reports/report.pdf --expires-in 86400 \
--endpoint-url https://s3.dockhive.io

See the Python and JavaScript SDKs page for how s3 is created.

Let a browser or another service upload one file straight to your bucket. Your server creates the URL; the client sends the file with PUT.

url = s3.generate_presigned_url(
"put_object",
Params={"Bucket": "my-app-uploads", "Key": "avatars/user-42.png", "ContentType": "image/png"},
ExpiresIn=600,
)

Upload with the same Content-Type you signed:

Terminal window
curl -X PUT -H "Content-Type: image/png" --upload-file avatar.png "$URL"
// In the browser
await fetch(url, { method: 'PUT', headers: { 'Content-Type': file.type }, body: file });

Uploads from a web page need a CORS rule on the bucket that allows your site’s origin and the PUT method. A single presigned PUT can carry up to 100 MB; for bigger files, presign each part of a multipart upload or upload from your server.

A POST policy lets an HTML form upload directly to a bucket, with rules you set: the key prefix, the content type, and a size range. Your server signs the policy and puts its fields into the form.

post = s3.generate_presigned_post(
Bucket="my-app-uploads",
Key="uploads/${filename}",
Fields={"Content-Type": "image/jpeg"},
Conditions=[
["starts-with", "$key", "uploads/"],
{"Content-Type": "image/jpeg"},
["content-length-range", 1, 10 * 1024 * 1024], # 1 byte to 10 MB
],
ExpiresIn=900,
)
# post["url"] is the bucket URL; post["fields"] are the hidden form fields
<form action="https://s3.dockhive.io/my-app-uploads" method="post" enctype="multipart/form-data">
<!-- one hidden input per entry in post["fields"]: key, Content-Type, policy,
x-amz-algorithm, x-amz-credential, x-amz-date, x-amz-signature -->
<input type="hidden" name="key" value="uploads/${filename}" />
<input type="hidden" name="Content-Type" value="image/jpeg" />
<input type="hidden" name="policy" value="..." />
<input type="hidden" name="x-amz-algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="x-amz-credential" value="..." />
<input type="hidden" name="x-amz-date" value="..." />
<input type="hidden" name="x-amz-signature" value="..." />
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>

The file field must come last. POST uploads can be up to 100 MB. To submit the form with JavaScript (fetch with FormData), add a CORS rule that allows POST from your site.

Presigned links to HTML and SVG files download the file instead of displaying it. See HTML and SVG files.