Skip to main content

Uploading to Your S3 Bucket

Use customS3PresignedUrl when you want PDFBolt to upload the generated PDF directly to your own S3-compatible storage. This keeps the final PDF in your storage environment, where you control retention, access policies, and downstream processing.

Endpoint support

customS3PresignedUrl works with /v1/sync and /v1/async on paid plans.

Omit this parameter to use PDFBolt's default storage – the PDF stays available via documentUrl for 24 hours.

How It Works

  1. Generate an HTTPS pre-signed PUT URL for the final PDF object in your S3-compatible bucket.
  2. Send the URL to PDFBolt as customS3PresignedUrl in a /v1/sync or /v1/async request.
  3. PDFBolt renders the PDF and uploads it to the pre-signed URL with an HTTP PUT.
  4. Your application reads and manages the PDF from your own bucket.

Pre-signed URL Requirements

The URL you pass to PDFBolt must:

  • Use HTTPS. HTTP URLs are rejected.
  • Be no longer than 2048 characters.
  • Allow PUT for the exact bucket and object key.
  • Remain valid long enough for the full PDF conversion and upload.
  • Accept Content-Type: application/pdf.
  • Accept the Content-Disposition header PDFBolt sends during upload.

By default, PDFBolt uploads with:

Content-Type: application/pdf
Content-Disposition: inline

If your request includes contentDisposition or filename, make sure the pre-signed URL allows the matching Content-Disposition header.

Supported S3-Compatible Storage

PDFBolt can upload to S3-compatible storage providers that support HTTPS pre-signed PUT URLs, including:

  • Amazon S3
  • Cloudflare R2
  • DigitalOcean Spaces
  • MinIO
  • Wasabi
  • Backblaze B2
More Providers

Other S3-compatible providers may work if they support HTTPS pre-signed PUT URLs and the required upload headers.

Example: Generating a Pre-signed URL in Node.js

Install the AWS SDK v3 packages:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
generatePresignedUrl.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

const s3 = new S3Client({
region: process.env.S3_REGION || 'us-east-1',
...(process.env.S3_ENDPOINT ? { endpoint: process.env.S3_ENDPOINT } : {}),
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
},
// Some S3-compatible providers, such as MinIO, require path-style URLs.
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true'
});

async function generatePresignedUrl(bucketName, objectKey) {
const command = new PutObjectCommand({
Bucket: bucketName,
Key: objectKey,
ContentType: 'application/pdf',
ContentDisposition: 'inline'
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
console.log(url);
return url;
}

generatePresignedUrl('your-bucket-name', `pdfbolt/document-${Date.now()}.pdf`)
.catch((error) => {
console.error('Error generating pre-signed URL:', error);
process.exit(1);
});
Run the script with your storage credentials
S3_REGION=us-east-1 \
S3_ACCESS_KEY_ID=your-access-key-id \
S3_SECRET_ACCESS_KEY=your-secret-access-key \
node generatePresignedUrl.js

For S3-compatible providers other than AWS S3, also set S3_ENDPOINT. For providers that require path-style URLs, set S3_FORCE_PATH_STYLE=true.

The output should look like this:

https://your-bucket.s3.amazonaws.com/pdfbolt/document-1714580000000.pdf?<presigned-query-params>
Pre-signed URL Best Practices
  • Generate a separate pre-signed URL for each PDF. Reusing the same bucket/key can overwrite an existing object.
  • Generate the URL on your server, not in browser code.
  • Keep storage credentials in environment variables or a secret manager.
  • Store the bucket and object key in your application so you can find the PDF later without relying on the pre-signed URL.
  • Set expiresIn long enough for the conversion to finish, especially for large pages or slow external assets.

Use the Pre-signed URL with PDFBolt

Sync Conversion

Use the generated URL as customS3PresignedUrl in the /v1/sync request body:

{
"url": "https://example.com",
"customS3PresignedUrl": "https://your-bucket.s3.amazonaws.com/pdfbolt/document.pdf?<presigned-query-params>"
}

When the upload succeeds, the response has documentUrl: null, expiresAt: null, and isCustomS3Bucket: true because the PDF is already in your bucket.

Sync response with custom S3
{
"requestId": "09dd133a-a064-44b6-80f5-b2a7571f77ed",
"status": "SUCCESS",
"errorCode": null,
"errorMessage": null,
"documentUrl": null,
"expiresAt": null,
"isAsync": false,
"duration": 664,
"documentSizeMb": 0.02,
"isCustomS3Bucket": true
}

Async Conversion

For /v1/async, include both webhook and customS3PresignedUrl:

{
"url": "https://example.com",
"webhook": "https://your-app.com/webhooks/pdfbolt",
"customS3PresignedUrl": "https://your-bucket.s3.amazonaws.com/pdfbolt/document.pdf?<presigned-query-params>"
}

The initial API response returns a requestId. When the conversion succeeds, PDFBolt sends a webhook with documentUrl: null, expiresAt: null, and isCustomS3Bucket: true because the PDF is already in your bucket.

Async webhook with custom S3
{
"requestId": "fadb0a9c-a5f5-4d15-8588-b15bde7c201d",
"status": "SUCCESS",
"errorCode": null,
"errorMessage": null,
"documentUrl": null,
"expiresAt": null,
"isAsync": true,
"duration": 601,
"documentSizeMb": 0.02,
"isCustomS3Bucket": true
}

See Async Workflow Details for the full async flow.

Troubleshooting

Upload Error

When PDFBolt receives a non-2xx response while uploading the PDF to your pre-signed URL, the conversion fails with errorCode: "CUSTOM_S3_UPLOAD_ERROR". The errorMessage includes the storage provider's response code and body.

This usually means one of the following:

  • The pre-signed URL expired before the PDF upload started.
  • The URL doesn't allow PUT for the exact bucket/key.
  • The signing identity doesn't have s3:PutObject or equivalent write permission.
  • The bucket or object key is wrong.
  • The provider rejected an upload header, usually Content-Type or Content-Disposition.
  • The bucket policy or provider allowlist rejects PDFBolt's upload request.

If your storage provider uses IP allowlisting, see PDFBolt IP Addresses.

Pre-signed URL Rejected Before Conversion

Make sure customS3PresignedUrl uses HTTPS and is no longer than 2048 characters. HTTP URLs and longer URLs are rejected before PDF generation starts.

Next Steps