Skip to content
Cloudflare Docs

Presigned URLs

Presigned URLs are an S3 concept for granting temporary access to objects without exposing your API credentials. A presigned URL includes signature parameters in the URL itself, authorizing anyone with the URL to perform a specific operation (like GetObject or PutObject) on a specific object until the URL expires.

They are ideal for granting temporary access to specific objects, such as allowing users to upload files directly to R2 or providing time-limited download links.

To generate a presigned URL, you specify:

  1. Resource identifier: Account ID, bucket name, and object path
  2. Operation: The S3 API operation permitted (GET, PUT, HEAD, or DELETE)
  3. Expiry: Timeout from 1 second to 7 days (604,800 seconds)

Presigned URLs are generated client-side with no communication with R2, requiring only your R2 API credentials and an implementation of the AWS Signature Version 4 signing algorithm.

Generate a presigned URL

Prerequisites

  • Account ID (for constructing the S3 endpoint URL)
  • R2 API token (Access Key ID and Secret Access Key)
  • AWS SDK or compatible S3 client library

SDK examples

TypeScript
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const S3 = new S3Client({
region: "auto", // Required by SDK but not used by R2
// Provide your Cloudflare account ID
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
// Retrieve your S3 API credentials for your R2 bucket via API tokens (see: https://developers.cloudflare.com/r2/api/tokens)
credentials: {
accessKeyId: '<ACCESS_KEY_ID>',
secretAccessKey: '<SECRET_ACCESS_KEY>',
},
});
// Generate presigned URL for reading (GET)
const getUrl = await getSignedUrl(
S3,
new GetObjectCommand({ Bucket: "my-bucket", Key: "image.png" }),
{ expiresIn: 3600 }, // Valid for 1 hour
);
// https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=...
// Generate presigned URL for writing (PUT)
// Specify ContentType to restrict uploads to a specific file type
const putUrl = await getSignedUrl(
S3,
new PutObjectCommand({
Bucket: "my-bucket",
Key: "image.png",
ContentType: "image/png",
}),
{ expiresIn: 3600 },
);

For complete examples and additional operations, refer to the SDK-specific documentation:

Best practices

When generating presigned URLs, you can limit abuse and misuse by:

  • Restricting Content-Type: Specify the allowed Content-Type in your SDK's parameters. The signature will include this header, so uploads will fail with a 403/SignatureDoesNotMatch error if the client sends a different Content-Type for an upload request.
  • Configuring CORS: If your presigned URLs will be used from a browser, set up CORS rules on your bucket to control which origins can make requests.

Using a presigned URL

Once generated, use a presigned URL like any HTTP endpoint. The signature is embedded in the URL, so no additional authentication headers are required.

Terminal window
# Download using a GET presigned URL
curl "https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=..."
# Upload using a PUT presigned URL
curl -X PUT "https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=..." \
--data-binary @image.png

You can also use presigned URLs directly in web browsers, mobile apps, or any HTTP client. The same presigned URL can be reused multiple times until it expires.

Presigned URL example

The following is an example of a presigned URL that was created using R2 API credentials and following the AWS Signature Version 4 signing process:

https://my-bucket.123456789abcdef0123456789abcdef.r2.cloudflarestorage.com/photos/cat.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=CFEXAMPLEKEY12345%2F20251201%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20251201T180512Z&X-Amz-Expires=3600&X-Amz-Signature=8c3ac40fa6c83d64b4516e0c9e5fa94c998bb79131be9ddadf90cefc5ec31033&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

In this example, this presigned url performs a GetObject on the object photos/cat.png within bucket my-bucket in the account with id 123456789abcdef0123456789abcdef. The key signature parameters that compose this presigned URL are:

  • X-Amz-Algorithm: Identifies the algorithm used to sign the URL.
  • X-Amz-Credential: Contains information about the credentials used to calculate the signature.
  • X-Amz-Date: The date and time (in ISO 8601 format) when the signature was created.
  • X-Amz-Expires: The duration in seconds that the presigned URL remains valid, starting from X-Amz-Date.
  • X-Amz-Signature: The signature proving the URL was signed using the secret key.
  • X-Amz-SignedHeaders: Lists the HTTP headers that were included in the signature calculation.

Supported operations

R2 supports presigned URLs for the following HTTP methods:

  • GET: Fetch an object from a bucket
  • HEAD: Fetch an object's metadata from a bucket
  • PUT: Upload an object to a bucket
  • DELETE: Delete an object from a bucket

POST (multipart form uploads via HTML forms) is not currently supported.

Security considerations

Treat presigned URLs as bearer tokens. Anyone with the URL can perform the specified operation until it expires. Share presigned URLs only with intended recipients and consider using short expiration times for sensitive operations.

Custom domains

Presigned URLs work with the S3 API domain (<ACCOUNT_ID>.r2.cloudflarestorage.com) and cannot be used with custom domains.

If you need authentication with R2 buckets accessed via custom domains (public buckets), use the WAF HMAC validation feature (requires Pro plan or above).