466 lines
9.7 KiB
Markdown
466 lines
9.7 KiB
Markdown
# R2 Workers API Complete Reference
|
|
|
|
**Last Updated**: 2025-10-21
|
|
**Official Docs**: https://developers.cloudflare.com/r2/api/workers/workers-api-reference/
|
|
|
|
---
|
|
|
|
## R2Bucket Methods
|
|
|
|
### put()
|
|
|
|
Upload an object to R2.
|
|
|
|
```typescript
|
|
put(
|
|
key: string,
|
|
value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,
|
|
options?: R2PutOptions
|
|
): Promise<R2Object | null>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Object key (path) in the bucket
|
|
- `value` - Object data
|
|
- `options` - Optional upload options
|
|
|
|
**Returns:**
|
|
- `R2Object` - Metadata of uploaded object
|
|
- `null` - If precondition failed (onlyIf clause)
|
|
|
|
**Options (R2PutOptions):**
|
|
```typescript
|
|
interface R2PutOptions {
|
|
httpMetadata?: R2HTTPMetadata;
|
|
customMetadata?: Record<string, string>;
|
|
md5?: ArrayBuffer;
|
|
sha1?: ArrayBuffer;
|
|
sha256?: ArrayBuffer;
|
|
sha384?: ArrayBuffer;
|
|
sha512?: ArrayBuffer;
|
|
onlyIf?: R2Conditional;
|
|
storageClass?: 'Standard';
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### get()
|
|
|
|
Download an object from R2.
|
|
|
|
```typescript
|
|
get(
|
|
key: string,
|
|
options?: R2GetOptions
|
|
): Promise<R2ObjectBody | null>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Object key (path) in the bucket
|
|
- `options` - Optional download options
|
|
|
|
**Returns:**
|
|
- `R2ObjectBody` - Object with metadata and body stream
|
|
- `null` - If object doesn't exist or precondition failed
|
|
|
|
**Options (R2GetOptions):**
|
|
```typescript
|
|
interface R2GetOptions {
|
|
onlyIf?: R2Conditional | Headers;
|
|
range?: R2Range;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### head()
|
|
|
|
Get object metadata without downloading body.
|
|
|
|
```typescript
|
|
head(key: string): Promise<R2Object | null>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Object key (path) in the bucket
|
|
|
|
**Returns:**
|
|
- `R2Object` - Object metadata only
|
|
- `null` - If object doesn't exist
|
|
|
|
**Use Cases:**
|
|
- Check if file exists
|
|
- Get file size
|
|
- Get last modified date
|
|
- Validate etag
|
|
|
|
---
|
|
|
|
### delete()
|
|
|
|
Delete one or more objects.
|
|
|
|
```typescript
|
|
delete(key: string | string[]): Promise<void>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Single key or array of keys (max 1000)
|
|
|
|
**Returns:**
|
|
- `void` - Always succeeds (idempotent)
|
|
|
|
**Notes:**
|
|
- No error if object doesn't exist
|
|
- Can delete up to 1000 objects at once
|
|
- Deletes are strongly consistent
|
|
|
|
---
|
|
|
|
### list()
|
|
|
|
List objects in the bucket.
|
|
|
|
```typescript
|
|
list(options?: R2ListOptions): Promise<R2Objects>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `options` - Optional listing options
|
|
|
|
**Returns:**
|
|
- `R2Objects` - List of objects and metadata
|
|
|
|
**Options (R2ListOptions):**
|
|
```typescript
|
|
interface R2ListOptions {
|
|
limit?: number; // Max 1000, default 1000
|
|
prefix?: string; // Filter by prefix
|
|
cursor?: string; // Pagination cursor
|
|
delimiter?: string; // Folder delimiter (usually '/')
|
|
include?: ('httpMetadata' | 'customMetadata')[];
|
|
}
|
|
```
|
|
|
|
**Response (R2Objects):**
|
|
```typescript
|
|
interface R2Objects {
|
|
objects: R2Object[]; // Array of objects
|
|
truncated: boolean; // true if more results exist
|
|
cursor?: string; // Cursor for next page
|
|
delimitedPrefixes: string[]; // "Folder" names (if delimiter used)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### createMultipartUpload()
|
|
|
|
Create a new multipart upload.
|
|
|
|
```typescript
|
|
createMultipartUpload(
|
|
key: string,
|
|
options?: R2MultipartOptions
|
|
): Promise<R2MultipartUpload>
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Object key for the upload
|
|
- `options` - Optional metadata
|
|
|
|
**Returns:**
|
|
- `R2MultipartUpload` - Object for managing the upload
|
|
|
|
**Options (R2MultipartOptions):**
|
|
```typescript
|
|
interface R2MultipartOptions {
|
|
httpMetadata?: R2HTTPMetadata;
|
|
customMetadata?: Record<string, string>;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### resumeMultipartUpload()
|
|
|
|
Resume an existing multipart upload.
|
|
|
|
```typescript
|
|
resumeMultipartUpload(
|
|
key: string,
|
|
uploadId: string
|
|
): R2MultipartUpload
|
|
```
|
|
|
|
**Parameters:**
|
|
- `key` - Object key for the upload
|
|
- `uploadId` - Upload ID from createMultipartUpload()
|
|
|
|
**Returns:**
|
|
- `R2MultipartUpload` - Object for managing the upload
|
|
|
|
**Notes:**
|
|
- Does NOT validate uploadId or key
|
|
- No network request made
|
|
- Use to continue an upload after Worker restart
|
|
|
|
---
|
|
|
|
## R2Object Interface
|
|
|
|
Metadata for an R2 object.
|
|
|
|
```typescript
|
|
interface R2Object {
|
|
key: string; // Object key
|
|
version: string; // Version ID
|
|
size: number; // Size in bytes
|
|
etag: string; // ETag (without quotes)
|
|
httpEtag: string; // ETag with quotes (RFC 9110)
|
|
uploaded: Date; // Upload timestamp
|
|
httpMetadata?: R2HTTPMetadata; // HTTP metadata
|
|
customMetadata?: Record<string, string>; // Custom metadata
|
|
range?: R2Range; // Range (if partial)
|
|
checksums?: R2Checksums; // Checksums
|
|
storageClass: 'Standard'; // Storage class
|
|
ssecKeyMd5?: string; // SSE-C key hash
|
|
|
|
writeHttpMetadata(headers: Headers): void; // Apply metadata to headers
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## R2ObjectBody Interface
|
|
|
|
Extends R2Object with body stream and read methods.
|
|
|
|
```typescript
|
|
interface R2ObjectBody extends R2Object {
|
|
body: ReadableStream; // Object body stream
|
|
bodyUsed: boolean; // Whether body consumed
|
|
|
|
arrayBuffer(): Promise<ArrayBuffer>; // Read as ArrayBuffer
|
|
text(): Promise<string>; // Read as text
|
|
json<T>(): Promise<T>; // Read as JSON
|
|
blob(): Promise<Blob>; // Read as Blob
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## R2MultipartUpload Interface
|
|
|
|
Manage a multipart upload.
|
|
|
|
```typescript
|
|
interface R2MultipartUpload {
|
|
key: string; // Object key
|
|
uploadId: string; // Upload ID
|
|
|
|
uploadPart(
|
|
partNumber: number,
|
|
value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,
|
|
options?: R2MultipartOptions
|
|
): Promise<R2UploadedPart>;
|
|
|
|
abort(): Promise<void>;
|
|
complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;
|
|
}
|
|
```
|
|
|
|
**Methods:**
|
|
|
|
- **uploadPart()** - Upload a single part (1-10,000)
|
|
- **abort()** - Cancel the multipart upload
|
|
- **complete()** - Finish upload with list of parts
|
|
|
|
---
|
|
|
|
## R2UploadedPart Interface
|
|
|
|
Metadata for an uploaded part.
|
|
|
|
```typescript
|
|
interface R2UploadedPart {
|
|
partNumber: number; // Part number (1-10,000)
|
|
etag: string; // Part ETag
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## R2HTTPMetadata Interface
|
|
|
|
HTTP headers for object.
|
|
|
|
```typescript
|
|
interface R2HTTPMetadata {
|
|
contentType?: string; // Content-Type header
|
|
contentLanguage?: string; // Content-Language header
|
|
contentDisposition?: string; // Content-Disposition header
|
|
contentEncoding?: string; // Content-Encoding header
|
|
cacheControl?: string; // Cache-Control header
|
|
cacheExpiry?: Date; // Expires header
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## R2Conditional Interface
|
|
|
|
Conditional operations (onlyIf clause).
|
|
|
|
```typescript
|
|
interface R2Conditional {
|
|
etagMatches?: string; // If-Match
|
|
etagDoesNotMatch?: string; // If-None-Match
|
|
uploadedBefore?: Date; // If-Unmodified-Since
|
|
uploadedAfter?: Date; // If-Modified-Since
|
|
}
|
|
```
|
|
|
|
**Alternatively, pass a Headers object with:**
|
|
- `If-Match`
|
|
- `If-None-Match`
|
|
- `If-Modified-Since`
|
|
- `If-Unmodified-Since`
|
|
|
|
---
|
|
|
|
## R2Range Interface
|
|
|
|
Byte range for partial downloads.
|
|
|
|
```typescript
|
|
interface R2Range {
|
|
offset?: number; // Start byte
|
|
length?: number; // Number of bytes
|
|
suffix?: number; // Last N bytes
|
|
}
|
|
```
|
|
|
|
**Examples:**
|
|
```typescript
|
|
// First 1000 bytes
|
|
{ offset: 0, length: 1000 }
|
|
|
|
// Bytes 100-200
|
|
{ offset: 100, length: 100 }
|
|
|
|
// From byte 1000 to end
|
|
{ offset: 1000 }
|
|
|
|
// Last 500 bytes
|
|
{ suffix: 500 }
|
|
```
|
|
|
|
---
|
|
|
|
## R2Checksums Interface
|
|
|
|
Stored checksums for object.
|
|
|
|
```typescript
|
|
interface R2Checksums {
|
|
md5?: ArrayBuffer;
|
|
sha1?: ArrayBuffer;
|
|
sha256?: ArrayBuffer;
|
|
sha384?: ArrayBuffer;
|
|
sha512?: ArrayBuffer;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Example
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
|
|
type Bindings = {
|
|
MY_BUCKET: R2Bucket;
|
|
};
|
|
|
|
const app = new Hono<{ Bindings: Bindings }>();
|
|
|
|
// Upload with all metadata
|
|
app.put('/files/:key', async (c) => {
|
|
const key = c.req.param('key');
|
|
const body = await c.req.arrayBuffer();
|
|
|
|
const object = await c.env.MY_BUCKET.put(key, body, {
|
|
httpMetadata: {
|
|
contentType: c.req.header('content-type') || 'application/octet-stream',
|
|
cacheControl: 'public, max-age=3600',
|
|
contentDisposition: `attachment; filename="${key}"`,
|
|
},
|
|
customMetadata: {
|
|
uploadedBy: 'api',
|
|
uploadedAt: new Date().toISOString(),
|
|
},
|
|
onlyIf: {
|
|
// Only upload if file doesn't exist
|
|
uploadedBefore: new Date('2020-01-01'),
|
|
},
|
|
});
|
|
|
|
if (!object) {
|
|
return c.json({ error: 'File already exists' }, 409);
|
|
}
|
|
|
|
return c.json({
|
|
key: object.key,
|
|
size: object.size,
|
|
etag: object.etag,
|
|
});
|
|
});
|
|
|
|
// Download with range support
|
|
app.get('/files/:key', async (c) => {
|
|
const key = c.req.param('key');
|
|
const rangeHeader = c.req.header('range');
|
|
|
|
let options: R2GetOptions | undefined;
|
|
|
|
if (rangeHeader) {
|
|
// Parse range header: bytes=0-1000
|
|
const match = rangeHeader.match(/bytes=(\d+)-(\d*)/);
|
|
if (match) {
|
|
const start = parseInt(match[1]);
|
|
const end = match[2] ? parseInt(match[2]) : undefined;
|
|
options = {
|
|
range: {
|
|
offset: start,
|
|
length: end ? end - start + 1 : undefined,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const object = await c.env.MY_BUCKET.get(key, options);
|
|
|
|
if (!object) {
|
|
return c.json({ error: 'Not found' }, 404);
|
|
}
|
|
|
|
const headers = new Headers();
|
|
object.writeHttpMetadata(headers);
|
|
headers.set('etag', object.httpEtag);
|
|
|
|
if (object.range) {
|
|
headers.set('content-range', `bytes ${object.range.offset}-${object.range.offset + object.range.length - 1}/${object.size}`);
|
|
return new Response(object.body, {
|
|
status: 206,
|
|
headers,
|
|
});
|
|
}
|
|
|
|
return new Response(object.body, { headers });
|
|
});
|
|
|
|
export default app;
|
|
```
|