Documentation Index Fetch the complete documentation index at: https://mintlify.com/cloudflare/workerd/llms.txt
Use this file to discover all available pages before exploring further.
R2 provides object storage with an S3-compatible API. Store and retrieve large files, images, videos, and other objects without egress fees.
Overview
R2 buckets provide:
S3-compatible object storage
HTTP metadata and custom metadata
Multipart uploads for large files
Conditional operations (etag matching)
Range requests for partial downloads
Object lifecycle management
Implementation: src/workerd/api/r2-bucket.h and r2-bucket.c++
Accessing R2 buckets
R2 buckets are bound to your worker in the environment:
export default {
async fetch ( request , env ) {
// Access via env binding
const object = await env . MY_BUCKET . get ( 'file.txt' );
return new Response ( object . body );
}
} ;
Uploading objects
Put object
Upload an object:
// Upload text
await env . MY_BUCKET . put ( 'file.txt' , 'Hello World' );
// Upload binary data
const buffer = new Uint8Array ([ 1 , 2 , 3 , 4 ]);
await env . MY_BUCKET . put ( 'data.bin' , buffer );
// Upload from a stream
const response = await fetch ( 'https://example.com/image.png' );
await env . MY_BUCKET . put ( 'image.png' , response . body );
Put with options
Set HTTP metadata and custom metadata:
await env . MY_BUCKET . put ( 'document.pdf' , pdfData , {
httpMetadata: {
contentType: 'application/pdf' ,
contentLanguage: 'en-US' ,
contentDisposition: 'attachment; filename="document.pdf"' ,
contentEncoding: 'gzip' ,
cacheControl: 'public, max-age=3600' ,
cacheExpiry: new Date ( Date . now () + 86400000 )
},
customMetadata: {
author: 'Alice' ,
department: 'Engineering' ,
version: '1.0'
}
});
Source: src/workerd/api/tests/r2-test.js:8
Put with checksums
Provide checksums for integrity verification:
await env . MY_BUCKET . put ( 'file.txt' , data , {
md5: 'base64-encoded-md5' ,
sha1: sha1Buffer ,
sha256: sha256Buffer
});
Downloading objects
Get object
Retrieve an object:
const object = await env . MY_BUCKET . get ( 'file.txt' );
if ( object === null ) {
return new Response ( 'Not found' , { status: 404 });
}
// Object properties
console . log ( 'Key:' , object . key );
console . log ( 'Size:' , object . size );
console . log ( 'ETag:' , object . etag );
console . log ( 'Uploaded:' , object . uploaded );
// Get body as text
const text = await object . text ();
// Or get body as stream
return new Response ( object . body );
Source: src/workerd/api/tests/r2-test.js:88
Get object body types
R2 objects support multiple body reading methods:
const object = await env . MY_BUCKET . get ( 'data' );
// Read as text
const text = await object . text ();
// Read as JSON
const json = await object . json ();
// Read as ArrayBuffer
const buffer = await object . arrayBuffer ();
// Read as Blob
const blob = await object . blob ();
// Access as ReadableStream
const stream = object . body ;
Get with options
Use conditional requests and range requests:
// Conditional get (only if etag matches)
const object = await env . MY_BUCKET . get ( 'file.txt' , {
onlyIf: {
etagMatches: '"abc123"'
}
});
// Range request (get bytes 0-999)
const object = await env . MY_BUCKET . get ( 'large-file.bin' , {
range: {
offset: 0 ,
length: 1000
}
});
// Get with suffix (last 1000 bytes)
const object = await env . MY_BUCKET . get ( 'log.txt' , {
range: {
suffix: 1000
}
});
Source: src/workerd/api/r2-bucket.h:78
Access HTTP metadata:
const object = await env . MY_BUCKET . get ( 'file.txt' );
console . log ( object . httpMetadata . contentType );
console . log ( object . httpMetadata . contentLanguage );
console . log ( object . httpMetadata . contentDisposition );
console . log ( object . httpMetadata . contentEncoding );
console . log ( object . httpMetadata . cacheControl );
console . log ( object . httpMetadata . cacheExpiry );
Access custom metadata:
const object = await env . MY_BUCKET . get ( 'document.pdf' );
const author = object . customMetadata . author ;
const version = object . customMetadata . version ;
Checksums
Verify object integrity:
const object = await env . MY_BUCKET . get ( 'file.txt' );
if ( object . checksums . md5 ) {
console . log ( 'MD5:' , object . checksums . md5 );
}
if ( object . checksums . sha256 ) {
console . log ( 'SHA-256:' , object . checksums . sha256 );
}
Source: src/workerd/api/r2-bucket.h:122
Head object
Get object metadata without downloading the body:
const object = await env . MY_BUCKET . head ( 'file.txt' );
if ( object === null ) {
return new Response ( 'Not found' , { status: 404 });
}
console . log ( 'Size:' , object . size );
console . log ( 'ETag:' , object . etag );
console . log ( 'Content-Type:' , object . httpMetadata . contentType );
// Note: object.body is null for head requests
Deleting objects
Delete single object
await env . MY_BUCKET . delete ( 'file.txt' );
Delete multiple objects
const keys = [ 'file1.txt' , 'file2.txt' , 'file3.txt' ];
await env . MY_BUCKET . delete ( keys );
Listing objects
List objects in a bucket:
// List all objects
const listed = await env . MY_BUCKET . list ();
for ( const object of listed . objects ) {
console . log ( 'Key:' , object . key );
console . log ( 'Size:' , object . size );
console . log ( 'Uploaded:' , object . uploaded );
}
// Check if more results available
if ( listed . truncated ) {
console . log ( 'More objects available' );
}
List with options
Filter and paginate list results:
const listed = await env . MY_BUCKET . list ({
prefix: 'images/' , // Only objects starting with 'images/'
delimiter: '/' , // Treat '/' as directory separator
limit: 100 , // Maximum objects to return
cursor: 'abc123' , // Pagination cursor
include: [ 'httpMetadata' , 'customMetadata' ]
});
// Process objects
for ( const object of listed . objects ) {
console . log ( object . key , object . httpMetadata ?. contentType );
}
// Process common prefixes (directories)
for ( const prefix of listed . delimitedPrefixes ) {
console . log ( 'Directory:' , prefix );
}
// Get next page
if ( listed . truncated ) {
const nextPage = await env . MY_BUCKET . list ({
cursor: listed . cursor
});
}
Multipart uploads
Upload large files in parts:
// Start multipart upload
const upload = await env . MY_BUCKET . createMultipartUpload ( 'large-file.bin' , {
httpMetadata: {
contentType: 'application/octet-stream'
}
});
const uploadId = upload . uploadId ;
const parts = [];
// Upload parts (minimum 5 MB each, except the last part)
for ( let i = 0 ; i < chunks . length ; i ++ ) {
const part = await upload . uploadPart ( i + 1 , chunks [ i ]);
parts . push ({
partNumber: i + 1 ,
etag: part . etag
});
}
// Complete the upload
const object = await upload . complete ( parts );
console . log ( 'Upload complete:' , object . key );
Resume multipart upload
Resume an existing upload:
const upload = env . MY_BUCKET . resumeMultipartUpload (
'large-file.bin' ,
uploadId
);
// Continue uploading parts...
Abort multipart upload
Cancel an upload:
Conditional operations
Conditional put
Only put if conditions are met:
try {
await env . MY_BUCKET . put ( 'file.txt' , 'new content' , {
onlyIf: {
etagMatches: '"old-etag"' // Only update if etag matches
}
});
} catch ( error ) {
console . log ( 'Condition not met' );
}
Conditional get
Only get if conditions are met:
const object = await env . MY_BUCKET . get ( 'file.txt' , {
onlyIf: {
etagDoesNotMatch: '"cached-etag"' , // Only get if modified
uploadedAfter: new Date ( '2024-01-01' )
}
});
if ( object === null ) {
console . log ( 'Condition not met' );
}
Source: src/workerd/api/r2-bucket.h:91
Patterns
export default {
async fetch ( request , env ) {
const url = new URL ( request . url );
const key = url . pathname . slice ( 1 );
const object = await env . BUCKET . get ( key );
if ( object === null ) {
return new Response ( 'Not found' , { status: 404 });
}
const headers = new Headers ();
object . writeHttpMetadata ( headers );
headers . set ( 'etag' , object . httpEtag );
return new Response ( object . body , { headers });
}
} ;
Upload with validation
async function uploadFile ( bucket , key , data ) {
// Calculate checksum
const hash = await crypto . subtle . digest ( 'SHA-256' , data );
const sha256 = new Uint8Array ( hash );
// Upload with checksum
await bucket . put ( key , data , {
sha256 ,
httpMetadata: {
contentType: 'application/octet-stream'
},
customMetadata: {
uploadedAt: new Date (). toISOString ()
}
});
}
Download with range support
export default {
async fetch ( request , env ) {
const range = request . headers . get ( 'Range' );
const key = new URL ( request . url ). pathname . slice ( 1 );
if ( range ) {
// Parse range header (simplified)
const [ start , end ] = range
. replace ( 'bytes=' , '' )
. split ( '-' )
. map ( Number );
const object = await env . BUCKET . get ( key , {
range: { offset: start , length: end - start + 1 }
});
if ( object === null ) {
return new Response ( 'Not found' , { status: 404 });
}
return new Response ( object . body , {
status: 206 ,
headers: {
'Content-Range' : `bytes ${ start } - ${ end } / ${ object . size } ` ,
'Content-Length' : object . size . toString ()
}
});
}
const object = await env . BUCKET . get ( key );
return new Response ( object . body );
}
} ;
Best practices
Set appropriate content types
Always set content type for better client handling: await env . BUCKET . put ( 'image.png' , imageData , {
httpMetadata: {
contentType: 'image/png'
}
});
Use checksums for integrity
Verify uploads with checksums: const hash = await crypto . subtle . digest ( 'SHA-256' , data );
await env . BUCKET . put ( key , data , {
sha256: new Uint8Array ( hash )
});
Use multipart for large files
Upload large files (> 100 MB) in parts: const upload = await env . BUCKET . createMultipartUpload ( key );
// Upload in 10 MB chunks
Use head for existence checks
Check if objects exist without downloading: const object = await env . BUCKET . head ( key );
if ( object === null ) {
// Object doesn't exist
}
Implementation details
R2 is implemented in:
src/workerd/api/r2-bucket.h / .c++ - Main R2 API (2000+ lines)
src/workerd/api/r2-multipart.h / .c++ - Multipart upload support
src/workerd/api/r2-admin.h / .c++ - Admin operations
src/workerd/api/r2-rpc.h / .c++ - RPC client
The R2 API communicates with R2 storage through HTTP client connections.
Definition from src/workerd/api/r2-bucket.h:42:
class R2Bucket : public jsg :: Object {
struct GetOptions {
jsg ::Optional < kj ::OneOf < Conditional, jsg ::Ref < Headers >>> onlyIf;
jsg ::Optional < kj ::OneOf < Range, jsg ::Ref < Headers >>> range;
};
jsg :: Promise < kj :: Maybe < jsg :: Ref < R2Object >>> get (
kj :: String key ,
jsg :: Optional < GetOptions > options );
jsg :: Promise < kj :: Maybe < jsg :: Ref < R2Object >>> put (
kj :: String key ,
Body body ,
jsg :: Optional < PutOptions > options );
};