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
- Configure your storage credentials in the Storage Dashboard
- Test the connection to verify credentials work
- 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:
- You receive the screenshot immediately
- A background job copies the screenshot to your bucket
- 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
- Log in to your Cloudflare Dashboard
- Navigate to R2 Object Storage in the sidebar
- Click Create bucket
- Enter a bucket name (e.g.,
my-screenshots) - Click Create bucket
Step 2: Create API credentials
- In the R2 section, click Manage R2 API Tokens
- Click Create API token
- Give it a name (e.g.,
RenderScreenshot) - Under Permissions, select Object Read & Write
- Under Specify bucket(s), select your bucket or choose Apply to all buckets
- Click Create API Token
- 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:
- In your bucket settings, go to Settings → Public access
- Click Allow Access to enable public access
- Optionally, connect a custom domain under Custom Domains
- Add the public URL base to your RenderScreenshot storage settings
Amazon S3
Step 1: Create an S3 bucket
- Log in to the AWS Console
- Click Create bucket
- Enter a bucket name and select a region
- Configure public access settings based on your needs
- Click Create bucket
Step 2: Create IAM credentials
- Go to IAM → Users → Create user
- Create a user with programmatic access
- Attach the following inline policy:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:PutObject", "s3:PutObjectAcl"], "Resource": "arn:aws:s3:::your-bucket-name/*" } ] }
- 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
- Log in to DigitalOcean
- Go to Spaces Object Storage
- Click Create a Space
- Select a datacenter region and name your Space
Step 2: Create API credentials
- Go to API → Spaces Keys
- Click Generate New Key
- 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
- Log in to Backblaze
- Click Create a Bucket
- Enter a bucket name and set file visibility
Step 2: Create application key
- Go to App Keys → Add a New Application Key
- Give it a name and select your bucket
- 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
- Log in to Wasabi Console
- Click Create Bucket
- Enter a name and select a region
Step 2: Create access keys
- Go to Access Keys → Create New Access Key
- 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_passedis 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
- Caching - Cache URL behavior and how it differs from custom storage
- POST /v1/screenshot - Screenshot endpoint with storage parameters
- Batch Screenshots - Batch processing with storage support
- Output Options - Image format and quality settings
- Response Headers - All response headers