Skip to main content
Presigned URLs embed temporary AWS credentials directly in a URL, allowing any holder of the URL to perform a specific S3 operation for a limited time — without needing AWS credentials of their own. Common uses include sharing downloadable files, allowing direct browser uploads, and granting access to private objects.

Installation

npm install @aws-sdk/s3-request-presigner @aws-sdk/client-s3

Generating a presigned GET URL

Use getSignedUrl with a GetObjectCommand to generate a URL that allows downloading a private S3 object:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });
const command = new GetObjectCommand({ Bucket: "my-bucket", Key: "my-file.pdf" });

const url = await getSignedUrl(client, command, { expiresIn: 3600 });
The expiresIn option sets the URL lifetime in seconds. It defaults to 900 (15 minutes) if not specified.

Generating a presigned PUT URL

Use PutObjectCommand to generate a URL that allows uploading directly to S3 from a browser or another service:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });
const command = new PutObjectCommand({ Bucket: "my-bucket", Key: "uploads/file.jpg" });

const url = await getSignedUrl(client, command, { expiresIn: 3600 });
// Share `url` with the client — they can HTTP PUT directly to it

Advanced signing options

Signing non-x-amz-* headers

Use signableHeaders to require specific headers (such as content-type) to be present and verified in the request:
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const command = new PutObjectCommand({
  Bucket: "my-bucket",
  Key: "my-key",
  ContentType: "image/jpeg",
});

const url = await getSignedUrl(client, command, {
  signableHeaders: new Set(["content-type"]),
  expiresIn: 3600,
});

Signing x-amz-* headers

By default, x-amz-* headers are hoisted out of the signed URL into the request headers. To force them into the signature (so the upload client must send them), use unhoistableHeaders:
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const command = new PutObjectCommand({
  Bucket: "my-bucket",
  Key: "my-key",
  ChecksumSHA256: sha,
});

const url = await getSignedUrl(client, command, {
  expiresIn: 3600,
  unhoistableHeaders: new Set(["x-amz-checksum-sha256"]),
});

Overriding the default hoisting behavior

Use hoistableHeaders to explicitly allow certain x-amz-* headers to be hoisted into query string parameters rather than enforced as request headers:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const command = new PutObjectCommand({
  Key: "my-key",
  Bucket: "my-bucket",
  ServerSideEncryption: "aws:kms",
  SSEKMSKeyId: "arn:aws:kms:us-west-2:0000:key/abcd-1234-abcd",
});

const url = await getSignedUrl(new S3Client(), command, {
  hoistableHeaders: new Set([
    "x-amz-server-side-encryption",
    "x-amz-server-side-encryption-aws-kms-key-id",
  ]),
});

Presigning from an existing request

If you already have a request object, you can presign it using S3RequestPresigner directly:
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
import { Hash } from "@aws-sdk/hash-node";

const signer = new S3RequestPresigner({
  region: regionProvider,
  credentials: credentialsProvider,
  sha256: Hash.bind(null, "sha256"), // In Node.js
  // sha256: Sha256 // In browsers (from @aws-crypto/sha256-browser)
});

const presigned = await signer.presign(request);
To reuse an existing S3 client’s configuration:
const signer = new S3RequestPresigner({
  ...s3Client.config,
});

Presigned POST with @aws-sdk/s3-presigned-post

For browser form-based uploads, use the separate @aws-sdk/s3-presigned-post package instead of a presigned PUT URL. It returns a url and a fields object that you embed in an HTML form or FormData.

Installation

npm install @aws-sdk/s3-presigned-post @aws-sdk/client-s3

Generating a presigned POST

import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-west-2" });

const Conditions = [
  { acl: "bucket-owner-full-control" },
  { bucket: "my-bucket" },
  ["starts-with", "$key", "user/uploads/"],
];

const Fields = {
  acl: "bucket-owner-full-control",
};

const { url, fields } = await createPresignedPost(client, {
  Bucket: "my-bucket",
  Key: "user/uploads/${filename}",
  Conditions,
  Fields,
  Expires: 600, // seconds, default is 3600
});
The Key value can contain ${filename}, which S3 replaces automatically with the name of the uploaded file.

Submitting from Node.js

const { createReadStream } = require("fs");
const FormData = require("form-data");

const form = new FormData();
Object.entries(fields).forEach(([field, value]) => {
  form.append(field, value);
});
form.append("file", createReadStream("path/to/a/file"));
form.submit(url, (err, res) => {
  // handle the response
});

Limitations

The credentials used to sign a URL must still be valid when the URL is used, not just when it is created. If credentials expire before the expiresIn period ends, the URL will stop working.
  • The maximum expiresIn for URLs signed with temporary credentials (e.g. IAM roles, STS tokens) is the remaining lifetime of those credentials, capped at 12 hours.
  • URLs signed with long-term IAM user credentials can have an expiresIn of up to 7 days.
  • Presigned URLs do not support multi-region access points.

Build docs developers (and LLMs) love