> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pdfshift.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Saving to Cloudflare R2

With PDFShift, you can save the converted PDF directly into your Cloudflare R2 bucket, allowing you to post process it or deliver it to your clients using your own system.

Unlike the other S3-compatible providers we support, R2 does not accept a bucket policy that grants PDFShift write access to your bucket. Instead, **you generate a pre-signed URL on your side** that authorizes a single `PutObject` upload, and you hand that URL to PDFShift as the destination.

This means you keep full control: the credentials never leave your infrastructure, and the URL you generate is short-lived and scoped to one specific object.

## 1. Generate a pre-signed URL

Following [Cloudflare's guide on pre-signed URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/), create an R2 API token (Access Key ID + Secret Access Key) and use it with any S3-compatible client to sign a `PutObject` request.

Point the S3 client at your R2 endpoint, which has the form:

```txt theme={null}
https://<ACCOUNT_ID>.r2.cloudflarestorage.com
```

Here is an example using Python and `boto3`:

```python theme={null}
import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    config=Config(signature_version="s3v4"),
    region_name="auto",
)

presigned_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "DOC-EXAMPLE-BUCKET",
        "Key": "pdfshift/upload/86aa3ede7d05.pdf",
    },
    ExpiresIn=3600,  # valid for 1 hour
)

print(presigned_url)
```

Or with the AWS SDK for JavaScript (v3):

```typescript theme={null}
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const S3 = new S3Client({
  region: "auto",
  endpoint: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
  credentials: {
    accessKeyId: "<ACCESS_KEY_ID>",
    secretAccessKey: "<SECRET_ACCESS_KEY>",
  },
});

const presignedUrl = await getSignedUrl(
  S3,
  new PutObjectCommand({
    Bucket: "DOC-EXAMPLE-BUCKET",
    Key: "pdfshift/upload/86aa3ede7d05.pdf",
  }),
  { expiresIn: 3600 }
);
```

<Warning>The pre-signed URL **must** authorize the `PutObject` action, since PDFShift uploads the generated PDF. A URL signed for any other action (such as `GetObject`) will be rejected.</Warning>

## 2. Pass the URL to PDFShift

The URL returned by the client will look like the following:

```txt theme={null}
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/DOC-EXAMPLE-BUCKET/pdfshift/upload/86aa3ede7d05.pdf?X-Amz-Algorithm=...&X-Amz-Credential=...&X-Amz-Signature=...
```

Use that full URL as the value of the `s3_destination` parameter. PDFShift detects the `r2.cloudflarestorage.com` host and uploads the converted PDF straight to your bucket using the signature you provided.

<Warning>Don't forget to change `DOC-EXAMPLE-BUCKET` to your appropriate bucket's name, and `<ACCOUNT_ID>` / the credentials to your own values!</Warning>

<note>A pre-signed URL is scoped to a single object key and expires after the delay you set (`ExpiresIn`, up to 7 days). Generate a fresh URL for each conversion, and pick an expiration long enough to cover the time between generating the URL and running the conversion.</note>
