boto3
You must generate an Access Key before getting started. All examples will utilize access_key_id and access_key_secret variables which represent the Access Key ID and Secret Access Key values you generated.
You must configure boto3 ↗ to use a preconstructed endpoint_url value. This can be done through any boto3 usage that accepts connection arguments; for example:
import boto3
s3 = boto3.resource('s3', # Provide your Cloudflare account ID endpoint_url = '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) aws_access_key_id = '<ACCESS_KEY_ID>', aws_secret_access_key = '<SECRET_ACCESS_KEY>')You may, however, omit the aws_access_key_id and aws_secret_access_key arguments and allow boto3 to rely on the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables ↗ instead.
An example script may look like the following:
import boto3
s3 = boto3.client( service_name="s3", # Provide your Cloudflare account ID endpoint_url='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) aws_access_key_id='<ACCESS_KEY_ID>', aws_secret_access_key='<SECRET_ACCESS_KEY>', region_name="auto", # Required by SDK but not used by R2)
# Get object informationobject_information = s3.head_object(Bucket='my-bucket', Key='dog.png')
# Upload/Update single files3.upload_fileobj(io.BytesIO(file_content), 'my-bucket', 'dog.png')
# Delete objects3.delete_object(Bucket='my-bucket', Key='dog.png')You can also generate presigned links that can be used to share public read or write access to a bucket temporarily.
import boto3
s3 = boto3.client( service_name="s3", # Provide your Cloudflare account ID endpoint_url='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) aws_access_key_id='<ACCESS_KEY_ID>', aws_secret_access_key='<SECRET_ACCESS_KEY>', region_name="auto", # Required by SDK but not used by R2)
# Generate presigned URL for reading (GET)# The ExpiresIn parameter determines how long the presigned link is valid (in seconds)get_url = s3.generate_presigned_url( 'get_object', Params={'Bucket': 'my-bucket', 'Key': 'dog.png'}, ExpiresIn=3600 # Valid for 1 hour)
print(get_url)
# Generate presigned URL for writing (PUT)# Specify ContentType to restrict uploads to a specific file typeput_url = s3.generate_presigned_url( 'put_object', Params={ 'Bucket': 'my-bucket', 'Key': 'dog.png', 'ContentType': 'image/png' }, ExpiresIn=3600)
print(put_url)https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature>https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=<signature>You can use the link generated by the put_object example to upload to the specified bucket and key, until the presigned link expires. When using a presigned URL with ContentType, the client must include a matching Content-Type header in the request.
curl -X PUT "https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=..." \ -H "Content-Type: image/png" \ --data-binary @dog.pngWhen generating presigned URLs for uploads, you can limit abuse and misuse by:
-
Restricting Content-Type: Specify the allowed content type in the presigned URL parameters. The upload will fail if the client sends a different
Content-Typeheader. -
Configuring CORS: Set up CORS rules on your bucket to control which origins can upload files. Configure CORS via the Cloudflare dashboard ↗ by adding a JSON policy to your bucket settings:
[ { "AllowedOrigins": ["https://example.com"], "AllowedMethods": ["PUT"], "AllowedHeaders": ["Content-Type"], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3600 }]Then generate a presigned URL with a Content-Type restriction:
# Generate a presigned URL with Content-Type restriction# The upload will only succeed if the client sends Content-Type: image/pngput_url = s3.generate_presigned_url( 'put_object', Params={ 'Bucket': 'my-bucket', 'Key': 'dog.png', 'ContentType': 'image/png' }, ExpiresIn=3600)When a client uses this presigned URL, they must:
- Make the request from an allowed origin (enforced by CORS)
- Include the
Content-Type: image/pngheader (enforced by the signature)
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-