```typescript // Basic await env.MY_BUCKET.put(key, value)
Scanned 5/31/2026
Install via CLI
openskills install tools-only/X-Skills# R2 API Reference
## PUT (Upload)
```typescript
// Basic
await env.MY_BUCKET.put(key, value)
// With metadata
await env.MY_BUCKET.put(key, value, {
httpMetadata: {
contentType: 'image/jpeg',
contentDisposition: 'attachment; filename="photo.jpg"',
cacheControl: 'max-age=3600',
},
customMetadata: { userId: '123', version: '2' },
storageClass: 'Standard', // or 'InfrequentAccess'
sha256: arrayBufferOrHex, // Integrity check
ssecKey: arrayBuffer32bytes, // SSE-C encryption
})
// Value types: ReadableStream | ArrayBuffer | string | Blob
```
## GET (Download)
```typescript
const object = await env.MY_BUCKET.get(key)
if (!object) return new Response('Not found', { status: 404 })
// Body: arrayBuffer(), text(), json(), blob(), body (ReadableStream)
// Ranged reads
const object = await env.MY_BUCKET.get(key, { range: { offset: 0, length: 1024 } })
// Conditional GET
const object = await env.MY_BUCKET.get(key, { onlyIf: { etagMatches: '"abc123"' } })
```
## HEAD (Metadata Only)
```typescript
const object = await env.MY_BUCKET.head(key) // Returns R2Object without body
```
## DELETE
```typescript
await env.MY_BUCKET.delete(key)
await env.MY_BUCKET.delete([key1, key2, key3]) // Batch (max 1000)
```
## LIST
```typescript
const listed = await env.MY_BUCKET.list({
limit: 1000,
prefix: 'photos/',
cursor: cursorFromPrevious,
delimiter: '/',
include: ['httpMetadata', 'customMetadata'],
})
// Pagination (always use truncated flag)
while (listed.truncated) {
const next = await env.MY_BUCKET.list({ cursor: listed.cursor })
listed.objects.push(...next.objects)
listed.truncated = next.truncated
listed.cursor = next.cursor
}
```
## Multipart Uploads
```typescript
const multipart = await env.MY_BUCKET.createMultipartUpload(key, {
httpMetadata: { contentType: 'video/mp4' },
})
const uploadedParts: R2UploadedPart[] = []
for (let i = 0; i < partCount; i++) {
const part = await multipart.uploadPart(i + 1, partData)
uploadedParts.push(part)
}
const object = await multipart.complete(uploadedParts)
// OR: await multipart.abort();
// Resume
const multipart = env.MY_BUCKET.resumeMultipartUpload(key, uploadId)
```
## Presigned URLs (S3 SDK)
```typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3 = new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: { accessKeyId: env.R2_ACCESS_KEY_ID, secretAccessKey: env.R2_SECRET_ACCESS_KEY },
})
const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({ Bucket: 'my-bucket', Key: key }), { expiresIn: 3600 })
return Response.json({ uploadUrl })
```
## TypeScript Interfaces
```typescript
interface R2Bucket {
head(key: string): Promise<R2Object | null>
get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>
put(
key: string,
value: ReadableStream | ArrayBuffer | string | Blob,
options?: R2PutOptions,
): Promise<R2Object | null>
delete(keys: string | string[]): Promise<void>
list(options?: R2ListOptions): Promise<R2Objects>
createMultipartUpload(key: string, options?: R2MultipartOptions): Promise<R2MultipartUpload>
resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload
}
interface R2Object {
key: string
version: string
size: number
etag: string
httpEtag: string // httpEtag is quoted, use for headers
uploaded: Date
httpMetadata?: R2HTTPMetadata
customMetadata?: Record<string, string>
storageClass: 'Standard' | 'InfrequentAccess'
checksums: R2Checksums
writeHttpMetadata(headers: Headers): void
}
interface R2ObjectBody extends R2Object {
body: ReadableStream
bodyUsed: boolean
arrayBuffer(): Promise<ArrayBuffer>
text(): Promise<string>
json<T>(): Promise<T>
blob(): Promise<Blob>
}
interface R2HTTPMetadata {
contentType?: string
contentDisposition?: string
contentEncoding?: string
contentLanguage?: string
cacheControl?: string
cacheExpiry?: Date
}
interface R2PutOptions {
httpMetadata?: R2HTTPMetadata | Headers
customMetadata?: Record<string, string>
sha256?: ArrayBuffer | string // Only ONE checksum allowed
storageClass?: 'Standard' | 'InfrequentAccess'
ssecKey?: ArrayBuffer
}
interface R2GetOptions {
onlyIf?: R2Conditional | Headers
range?: R2Range | Headers
ssecKey?: ArrayBuffer
}
interface R2ListOptions {
limit?: number
prefix?: string
cursor?: string
delimiter?: string
startAfter?: string
include?: ('httpMetadata' | 'customMetadata')[]
}
interface R2Objects {
objects: R2Object[]
truncated: boolean
cursor?: string
delimitedPrefixes: string[]
}
interface R2Conditional {
etagMatches?: string
etagDoesNotMatch?: string
uploadedBefore?: Date
uploadedAfter?: Date
}
interface R2Range {
offset?: number
length?: number
suffix?: number
}
interface R2Checksums {
md5?: ArrayBuffer
sha1?: ArrayBuffer
sha256?: ArrayBuffer
sha384?: ArrayBuffer
sha512?: ArrayBuffer
}
interface R2MultipartUpload {
key: string
uploadId: string
uploadPart(partNumber: number, value: ReadableStream | ArrayBuffer | string | Blob): Promise<R2UploadedPart>
abort(): Promise<void>
complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>
}
interface R2UploadedPart {
partNumber: number
etag: string
}
```
## CLI Operations
```bash
wrangler r2 object put my-bucket/file.txt --file=./local.txt
wrangler r2 object get my-bucket/file.txt --file=./download.txt
wrangler r2 object delete my-bucket/file.txt
wrangler r2 object list my-bucket --prefix=photos/
```
No comments yet. Be the first to comment!