High-level Cloudflare R2 storage API wrapper for generating pre-signed URLs and performing object operations
Direct file operations allow you to upload, download, and delete files from your application code without generating pre-signed URLs. This approach is suitable for server-side operations where credentials can be kept secure.
Core Capabilities:
Key Methods:
uploadFile(key: string, file: Blob | File | ArrayBuffer | string, options: UploadFileOptions): Promise<UploadResult> - Upload file directlygetObject(key: string): Promise<R2Object & { body: Response }> - Retrieve object with metadata and bodydeleteObject(key: string): Promise<void> - Delete object from bucketKey Interfaces:
UploadFileOptions - contentType: string, metadata?: Record<string, string>UploadResult - key: string, contentType: string, fileSize: number, etag?: stringR2Object - key: string, contentType?: string, size: number, lastModified: Date, etag?: string, metadata?: Record<string, string>Default Behaviors:
uploadFile() overwrites existing objects with same key (no versioning)getObject() throws error if object doesn't existdeleteObject() succeeds even if object doesn't exist (idempotent)uploadFile() requires contentType (not optional)metadata is optional in UploadFileOptions (undefined if not provided)etag may be undefined in UploadResult (depends on R2 response)getObject() returns R2Object & { body: Response } - body is a fetch Response object.blob(), .text(), or .arrayBuffer())x-amz-meta- prefix (S3-compatible)Supported File Types:
Blob - Standard Blob objectFile - File object (extends Blob)ArrayBuffer - Binary data bufferstring - Text content (converted to UTF-8 bytes)Threading Model:
getObject() body consumption is not thread-safe (consume once per call)Lifecycle:
getObject()Common Patterns:
uploadFile() call with credentialsgetObject() → Read body → Process contentPromise.all() for multiple operationsgetObject() to avoid errorsIntegration Points:
x-amz-meta-* headersCritical Edge Cases:
getObject() throws error (use objectExists() first)uploadFile() silently overwrites (no warning)deleteObject() succeeds (idempotent)bucket.exists() first)obj.body.clone() to read body multiple timesException Handling:
Error objectsgetObject() throws error if object doesn't existuploadFile() throws error on network failure or invalid credentialsdeleteObject() throws error on network failure (but succeeds if object doesn't exist)objectExists() before getObject() to avoid errorsUpload a file directly to R2 storage with optional metadata.
/**
* Upload a file directly to R2
* @param key - Object key (filename)
* @param file - File content (Blob, File, ArrayBuffer, or string)
* @param options - Upload options including content type and optional metadata
* @returns Upload result with metadata
*/
uploadFile(
key: string,
file: Blob | File | ArrayBuffer | string,
options: UploadFileOptions
): Promise<UploadResult>;
interface UploadFileOptions {
/** Content type (MIME type) of the file */
contentType: string;
/** Optional metadata headers */
metadata?: Record<string, string>;
}
interface UploadResult {
/** Object key */
key: string;
/** Content type */
contentType: string;
/** File size in bytes */
fileSize: number;
/** ETag from R2 response */
etag?: string;
}Supported file types:
BlobFileArrayBufferstringUsage Examples:
import { R2Client } from '@cfkit/r2';
const r2 = new R2Client({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});
const bucket = r2.bucket('gallery');
// Upload a File object
const file = new File(['content'], 'photo.jpg', { type: 'image/jpeg' });
const result = await bucket.uploadFile('photo.jpg', file, {
contentType: 'image/jpeg',
metadata: {
'original-filename': 'vacation-photo.jpg'
}
});
console.log(`Uploaded ${result.key} (${result.fileSize} bytes)`);
console.log(`ETag: ${result.etag}`);
// Upload a Blob
const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
await bucket.uploadFile('hello.txt', blob, {
contentType: 'text/plain'
});
// Upload a string
await bucket.uploadFile('data.txt', 'Some text content', {
contentType: 'text/plain',
metadata: {
'uploaded-at': new Date().toISOString()
}
});
// Upload an ArrayBuffer
const buffer = new ArrayBuffer(8);
await bucket.uploadFile('data.bin', buffer, {
contentType: 'application/octet-stream'
});Retrieve an object from R2 storage, including its metadata and content.
/**
* Get an object from the bucket
* @param key - Object key (filename)
* @returns Object metadata and content
*/
getObject(key: string): Promise<R2Object & { body: Response }>;
interface R2Object {
/** Object key */
key: string;
/** Content type */
contentType?: string;
/** File size in bytes */
size: number;
/** Last modified date */
lastModified: Date;
/** ETag */
etag?: string;
/** Custom metadata */
metadata?: Record<string, string>;
}Usage Examples:
import { R2Client } from '@cfkit/r2';
const r2 = new R2Client({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});
const bucket = r2.bucket('gallery');
// Get object and read as blob
const obj = await bucket.getObject('photo.jpg');
const blob = await obj.body.blob();
console.log('Key:', obj.key); // 'photo.jpg'
console.log('Content Type:', obj.contentType); // 'image/jpeg'
console.log('Size:', obj.size); // File size in bytes
console.log('Last Modified:', obj.lastModified); // Date object
console.log('ETag:', obj.etag); // ETag value
console.log('Metadata:', obj.metadata); // Custom metadata
// Get object and read as text
const textObj = await bucket.getObject('data.txt');
const text = await textObj.body.text();
console.log('Content:', text);
// Get object and read as array buffer
const binObj = await bucket.getObject('data.bin');
const arrayBuffer = await binObj.body.arrayBuffer();
// Access custom metadata
const obj2 = await bucket.getObject('photo.jpg');
if (obj2.metadata) {
console.log('Original filename:', obj2.metadata['original-filename']);
console.log('Uploaded by:', obj2.metadata['uploaded-by']);
}
// Check existence before getting (recommended)
if (await bucket.objectExists('photo.jpg')) {
const obj = await bucket.getObject('photo.jpg');
const blob = await obj.body.blob();
// Process blob
} else {
console.log('Object does not exist');
}
// Clone body to read multiple times
const obj3 = await bucket.getObject('photo.jpg');
const clonedBody = obj3.body.clone();
const blob1 = await obj3.body.blob();
const blob2 = await clonedBody.blob();Important Notes:
.blob(), .text(), or .arrayBuffer())const clonedBody = obj.body.clone();Response object with all Response methods availableDelete an object from R2 storage.
/**
* Delete an object from the bucket
* @param key - Object key (filename)
* @returns void
*/
deleteObject(key: string): Promise<void>;Usage Examples:
import { R2Client } from '@cfkit/r2';
const r2 = new R2Client({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});
const bucket = r2.bucket('gallery');
// Delete an object
await bucket.deleteObject('photo.jpg');
console.log('Object deleted');
// Delete multiple objects
const keysToDelete = ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'];
await Promise.all(
keysToDelete.map(key => bucket.deleteObject(key))
);
// Delete with existence check
const exists = await bucket.objectExists('photo.jpg');
if (exists) {
await bucket.deleteObject('photo.jpg');
console.log('Object deleted');
} else {
console.log('Object does not exist');
}
// Delete is idempotent (safe to call multiple times)
await bucket.deleteObject('photo.jpg'); // First call
await bucket.deleteObject('photo.jpg'); // Second call succeeds (no error)All file operations may throw errors. Always wrap them in try-catch blocks:
try {
const result = await bucket.uploadFile('file.jpg', file, {
contentType: 'image/jpeg'
});
console.log('Upload successful:', result.key);
} catch (error) {
if (error instanceof Error) {
console.error('Upload failed:', error.message);
}
}Error Handling Patterns:
// Upload with error handling
try {
const result = await bucket.uploadFile('file.jpg', file, {
contentType: 'image/jpeg',
metadata: { 'uploaded-by': 'user-123' }
});
console.log('Uploaded:', result.key, result.fileSize, 'bytes');
} catch (error) {
if (error instanceof Error) {
console.error('Upload failed:', error.message);
// Handle error (retry, log, notify user, etc.)
}
}
// Get object with existence check
try {
if (await bucket.objectExists('photo.jpg')) {
const obj = await bucket.getObject('photo.jpg');
const blob = await obj.body.blob();
// Process blob
} else {
console.log('Object does not exist');
}
} catch (error) {
if (error instanceof Error) {
console.error('Failed to get object:', error.message);
}
}
// Delete with error handling
try {
await bucket.deleteObject('photo.jpg');
console.log('Deleted successfully');
} catch (error) {
if (error instanceof Error) {
console.error('Delete failed:', error.message);
}
}Common error scenarios:
bucket.exists() first)getObject() throws error (use objectExists() first)Install with Tessl CLI
npx tessl i tessl/npm-cfkit--r2