# 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; get(key: string, options?: R2GetOptions): Promise; put(key: string, value: ReadableStream | ArrayBuffer | string | Blob, options?: R2PutOptions): Promise; delete(keys: string | string[]): Promise; list(options?: R2ListOptions): Promise; createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; 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; storageClass: 'Standard' | 'InfrequentAccess'; checksums: R2Checksums; writeHttpMetadata(headers: Headers): void; } interface R2ObjectBody extends R2Object { body: ReadableStream; bodyUsed: boolean; arrayBuffer(): Promise; text(): Promise; json(): Promise; blob(): Promise; } interface R2HTTPMetadata { contentType?: string; contentDisposition?: string; contentEncoding?: string; contentLanguage?: string; cacheControl?: string; cacheExpiry?: Date; } interface R2PutOptions { httpMetadata?: R2HTTPMetadata | Headers; customMetadata?: Record; 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; abort(): Promise; complete(uploadedParts: R2UploadedPart[]): Promise; } 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/ ```