Storage Options

Upload screenshots to your own S3-compatible storage bucket. Screenshots are always stored in RenderScreenshot's CDN first, then copied to your bucket asynchronously via a background job.

Paid Plans Only

Custom storage is available on Starter, Growth, and Scale plans. Upgrade your plan to enable this feature.

Supported Providers

Works with any S3-compatible storage:

  • Amazon S3
  • Cloudflare R2
  • DigitalOcean Spaces
  • Backblaze B2
  • MinIO
  • Wasabi
  • Any other S3-compatible storage

Prerequisites

  1. Configure your storage credentials in the Storage Dashboard
  2. Test the connection to verify credentials work
  3. Ensure your bucket is accessible with the provided credentials

Full Configuration

{
  "url": "https://example.com",
  "storage": {
    "enabled": true,
    "path": "screenshots/{year}/{month}/{day}/{hash}.{ext}",
    "acl": "public-read"
  }
}

Parameters

Parameter Type Default Description
enabled boolean true Upload to configured storage (set false to skip for this request)
path string Account default Storage path template (overrides account default)
acl string Account default Access control: private or public-read

When your account has storage configured and enabled, screenshots are automatically uploaded. Use storage.enabled: false to skip storage for a specific request.

Path Templates

Customize where screenshots are stored using template variables:

{
  "storage": {
    "path": "screenshots/{year}/{month}/{day}/{hash}.{ext}"
  }
}

Available Variables

Variable Description Example
{hash} Content hash (required) a1b2c3d4
{ext} File extension (required) png, jpeg, pdf
{year} Year (4 digits) 2026
{month} Month (01-12) 01
{day} Day (01-31) 27
{timestamp} Unix timestamp 1706400000
{domain} Target URL domain example.com
{uuid} Random UUID 550e8400-e29b-41d4-a716-446655440000

Required: Path templates must include both {hash} and {ext} variables.

Path Examples

Organized by date: screenshots/{year}/{month}/{day}/{hash}.{ext} → screenshots/2026/01/27/a1b2c3d4.png

Organized by domain: captures/{domain}/{hash}.{ext} → captures/example.com/a1b2c3d4.png

With UUID for uniqueness: shots/{uuid}.{ext} → shots/550e8400-e29b-41d4-a716-446655440000.png

Simple flat structure: {hash}.{ext} → a1b2c3d4.png

Access Control

Private (Default)

Files are private and require authentication to access:

{
  "storage": {
    "acl": "private"
  }
}

Public Read

Files are publicly accessible via direct URL:

{
  "storage": {
    "acl": "public-read"
  }
}

Async Upload Behavior

Storage uploads happen asynchronously after the screenshot response is returned:

  1. You receive the screenshot immediately
  2. A background job copies the screenshot to your bucket
  3. The upload result is stored in the screenshot request record

This means you get fast response times without waiting for the upload to complete.

Dashboard Configuration

Configure your storage settings at /dashboard/storage:

Field Required Description
Bucket Name Yes Your storage bucket name
Endpoint URL For non-AWS Full endpoint URL (leave blank for AWS S3)
Region For AWS S3 AWS region (e.g., us-east-1)
Access Key ID Yes Your storage access key
Secret Access Key Yes Your storage secret key

Endpoint URLs by Provider

Provider Endpoint URL Format
Amazon S3 Leave blank (uses region)
Cloudflare R2 https://<account_id>.r2.cloudflarestorage.com
DigitalOcean Spaces https://<region>.digitaloceanspaces.com
Backblaze B2 https://s3.<region>.backblazeb2.com
Wasabi https://s3.<region>.wasabisys.com
MinIO Your MinIO server URL

Provider Setup Guides

Cloudflare R2

Cloudflare R2 is S3-compatible object storage with zero egress fees.

Step 1: Create an R2 bucket

  1. Log in to your Cloudflare Dashboard
  2. Navigate to R2 Object Storage in the sidebar
  3. Click Create bucket
  4. Enter a bucket name (e.g., my-screenshots)
  5. Click Create bucket

Step 2: Create API credentials

  1. In the R2 section, click Manage R2 API Tokens
  2. Click Create API token
  3. Give it a name (e.g., RenderScreenshot)
  4. Under Permissions, select Object Read & Write
  5. Under Specify bucket(s), select your bucket or choose Apply to all buckets
  6. Click Create API Token
  7. Copy the Access Key ID and Secret Access Key (you won't see the secret again)

Step 3: Find your Account ID

Your Account ID is in the R2 overview page URL or in the sidebar. It's a 32-character hexadecimal string.

Step 4: Configure in RenderScreenshot

Field Value
Bucket Name my-screenshots
Endpoint URL https://<account_id>.r2.cloudflarestorage.com
Region Leave blank
Access Key ID Your R2 API token Access Key ID
Secret Access Key Your R2 API token Secret Access Key

R2 Note: Leave the Region field blank. R2 automatically determines the region. The endpoint URL format is https://<account_id>.r2.cloudflarestorage.com where <account_id> is your 32-character Cloudflare account ID.

Optional: Set up a public bucket with custom domain

If you want screenshots to be publicly accessible:

  1. In your bucket settings, go to SettingsPublic access
  2. Click Allow Access to enable public access
  3. Optionally, connect a custom domain under Custom Domains
  4. Add the public URL base to your RenderScreenshot storage settings

Amazon S3

Step 1: Create an S3 bucket

  1. Log in to the AWS Console
  2. Click Create bucket
  3. Enter a bucket name and select a region
  4. Configure public access settings based on your needs
  5. Click Create bucket

Step 2: Create IAM credentials

  1. Go to IAMUsersCreate user
  2. Create a user with programmatic access
  3. Attach the following inline policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:PutObjectAcl"],
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
  1. Save the Access Key ID and Secret Access Key

Step 3: Configure in RenderScreenshot

Field Value
Bucket Name Your bucket name
Endpoint URL Leave blank
Region Your bucket region (e.g., us-east-1)
Access Key ID Your IAM Access Key ID
Secret Access Key Your IAM Secret Access Key

DigitalOcean Spaces

Step 1: Create a Space

  1. Log in to DigitalOcean
  2. Go to Spaces Object Storage
  3. Click Create a Space
  4. Select a datacenter region and name your Space

Step 2: Create API credentials

  1. Go to APISpaces Keys
  2. Click Generate New Key
  3. Copy the Key and Secret

Step 3: Configure in RenderScreenshot

Field Value
Bucket Name Your Space name
Endpoint URL https://<region>.digitaloceanspaces.com
Region Your Space region (e.g., nyc3, sfo3, ams3)
Access Key ID Your Spaces Key
Secret Access Key Your Spaces Secret

Backblaze B2

Step 1: Create a B2 bucket

  1. Log in to Backblaze
  2. Click Create a Bucket
  3. Enter a bucket name and set file visibility

Step 2: Create application key

  1. Go to App KeysAdd a New Application Key
  2. Give it a name and select your bucket
  3. Copy the keyID and applicationKey

Step 3: Configure in RenderScreenshot

Field Value
Bucket Name Your B2 bucket name
Endpoint URL https://s3.<region>.backblazeb2.com
Region Your bucket region (e.g., us-west-004)
Access Key ID Your B2 keyID
Secret Access Key Your B2 applicationKey

B2 Note: Find your bucket's region in the bucket details. The endpoint format is s3.<region>.backblazeb2.com.


Wasabi

Step 1: Create a bucket

  1. Log in to Wasabi Console
  2. Click Create Bucket
  3. Enter a name and select a region

Step 2: Create access keys

  1. Go to Access KeysCreate New Access Key
  2. Copy the Access Key and Secret Key

Step 3: Configure in RenderScreenshot

Field Value
Bucket Name Your Wasabi bucket name
Endpoint URL https://s3.<region>.wasabisys.com
Region Your bucket region (e.g., us-east-1, eu-central-1)
Access Key ID Your Wasabi Access Key
Secret Access Key Your Wasabi Secret Key

MinIO (Self-Hosted)

For self-hosted MinIO instances:

Field Value
Bucket Name Your MinIO bucket name
Endpoint URL Your MinIO server URL (e.g., https://minio.example.com)
Region Usually us-east-1 (MinIO default)
Access Key ID Your MinIO Access Key
Secret Access Key Your MinIO Secret Key

Examples

Basic Storage Upload

curl

curl -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer rs_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "preset": "og_card"
  }'

Java

import com.renderscreenshot.sdk.Client;
import com.renderscreenshot.sdk.TakeOptions;

Client client = new Client("rs_live_xxxxx");

// When storage is configured for your account, uploads happen automatically
byte[] image = client.take(
    TakeOptions.url("https://example.com").preset("og_card")
);

Node.js

import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client('rs_live_xxxxx');

// When storage is configured for your account, uploads happen automatically
const image = await client.take(
  TakeOptions.url('https://example.com').preset('og_card')
);

PHP

use RenderScreenshot\Client;
use RenderScreenshot\TakeOptions;

$client = new Client('rs_live_xxxxx');

// When storage is configured for your account, uploads happen automatically
$image = $client->take(
    TakeOptions::url('https://example.com')->preset('og_card')
);

Python

from renderscreenshot import Client, TakeOptions

client = Client('rs_live_xxxxx')

# When storage is configured for your account, uploads happen automatically
image = client.take(
    TakeOptions.url('https://example.com').preset('og_card')
)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client('rs_live_xxxxx')

# When storage is configured for your account, uploads happen automatically
image = client.take(
  RenderScreenshot::TakeOptions
    .url('https://example.com')
    .preset('og_card')
)

Go

client, _ := rs.New("rs_live_xxxxx")

// When storage is configured for your account, uploads happen automatically
image, _ := client.Take(context.Background(),
    rs.URL("https://example.com").Preset("og_card"),
)

When storage is configured for your account, uploads happen automatically.

Custom Path Template

curl

curl -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer rs_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "storage": {
      "path": "og-images/{domain}/{hash}.{ext}",
      "acl": "public-read"
    }
  }'

Java

import com.renderscreenshot.sdk.Client;
import com.renderscreenshot.sdk.TakeOptions;

Client client = new Client("rs_live_xxxxx");

byte[] image = client.take(
    TakeOptions.url("https://example.com")
        .storageEnabled()
        .storagePath("og-images/{domain}/{hash}.{ext}")
        .storageAcl("public-read")
);

Node.js

import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client('rs_live_xxxxx');

const image = await client.take(
  TakeOptions.url('https://example.com')
    .storageEnabled()
    .storagePath('og-images/{domain}/{hash}.{ext}')
    .storageAcl('public-read')
);

PHP

use RenderScreenshot\Client;
use RenderScreenshot\TakeOptions;

$client = new Client('rs_live_xxxxx');

$image = $client->take(
    TakeOptions::url('https://example.com')
        ->storageEnabled()
        ->storagePath('og-images/{domain}/{hash}.{ext}')
        ->storageAcl('public-read')
);

Python

from renderscreenshot import Client, TakeOptions

client = Client('rs_live_xxxxx')

image = client.take(
    TakeOptions.url('https://example.com')
    .storage_enabled()
    .storage_path('og-images/{domain}/{hash}.{ext}')
    .storage_acl('public-read')
)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client('rs_live_xxxxx')

image = client.take(
  RenderScreenshot::TakeOptions
    .url('https://example.com')
    .storage_enabled
    .storage_path('og-images/{domain}/{hash}.{ext}')
    .storage_acl('public-read')
)

Go

client, _ := rs.New("rs_live_xxxxx")

image, _ := client.Take(context.Background(),
    rs.URL("https://example.com").
        StorageEnabled().
        StoragePath("og-images/{domain}/{hash}.{ext}").
        StorageACL(rs.ACLPublicRead),
)

Skip Storage for One Request

curl

curl -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer rs_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "storage": {
      "enabled": false
    }
  }'

Node.js

import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client('rs_live_xxxxx');

// Explicitly disable storage for this request
const options = TakeOptions.url('https://example.com');
// Note: Use the raw config to set storage.enabled = false
const image = await client.take(
  TakeOptions.from({ url: 'https://example.com', storage: { enabled: false } })
);

PHP

use RenderScreenshot\Client;

$client = new Client('rs_live_xxxxx');

// Explicitly disable storage for this request
$image = $client->takeRaw([
    'url' => 'https://example.com',
    'storage' => ['enabled' => false]
]);

Python

from renderscreenshot import Client, TakeOptions

client = Client('rs_live_xxxxx')

# Explicitly disable storage for this request
# Note: Use the raw config to set storage.enabled = false
image = client.take(
    TakeOptions.from_config({'url': 'https://example.com', 'storage': {'enabled': False}})
)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client('rs_live_xxxxx')

# Explicitly disable storage for this request
image = client.take(
  RenderScreenshot::TakeOptions
    .from(url: 'https://example.com', storage: { enabled: false })
)

Go

client, _ := rs.New("rs_live_xxxxx")

// Explicitly disable storage for this request
image, _ := client.Take(context.Background(),
    rs.FromConfig(map[string]interface{}{
        "url": "https://example.com",
        "storage": map[string]interface{}{"enabled": false},
    }),
)

Storage vs Cache URL

Feature Cache URL Custom Storage
Provider RenderScreenshot CDN Your S3-compatible bucket
Lifetime TTL-based (max 30 days) Permanent (you control)
Control Managed by us Full control
Cost Included in plan Your storage costs
Response time Immediate Async (background job)

Use cache URL for temporary/dynamic content where you don't need permanent storage. Use custom storage when you need permanent archives or want screenshots in your own infrastructure.

Troubleshooting

Connection Test Failed

"Access denied" or "Invalid credentials" - Verify your Access Key ID and Secret Access Key are correct - Check that your API token/key has write permissions to the bucket - For R2: Ensure the token has "Object Read & Write" permission

"Bucket not found" - Double-check the bucket name (it's case-sensitive) - Verify the bucket exists in your storage provider - For R2: Make sure you're using the correct Account ID in the endpoint URL

"Invalid endpoint" - Ensure the endpoint URL includes https:// - Check for typos in the endpoint URL - For R2: The format is https://<account_id>.r2.cloudflarestorage.com

Uploads Not Appearing

  • Uploads happen asynchronously after the API response
  • Check that storage is enabled in your dashboard settings
  • Verify validation_passed is true (test connection first)
  • Ensure your account is on a paid plan (Starter, Growth, or Scale)

ACL Errors

"AccessControlListNotSupported" - Some providers (like R2) don't support ACLs - Leave the ACL setting as private or configure bucket-level public access instead - For R2: Use the Cloudflare dashboard to enable public access on the bucket

Path Template Issues

"Invalid path template" - Path must include both {hash} and {ext} variables - Check for typos in variable names - Valid variables: {hash}, {ext}, {year}, {month}, {day}, {timestamp}, {domain}, {uuid}

See Also

Was this page helpful?