Skip to navigation

Storage

R2 object storage with presigned uploads and downloads

Overview

Each project can have one or more storage buckets backed by Cloudflare R2. The SDK provides methods to manage buckets, list objects, and generate presigned URLs for secure uploads and downloads.

All storage methods are available on r.storage.

Methods

MethodDescription
listBuckets(params)List all buckets for a project.
getBucket(name, params)Get a specific bucket by name.
createBucket(input)Create a new bucket.
ensureBucket(input)Ensure a bucket exists (create if not). Idempotent.
listItems(params)List objects in a bucket.
createFolder(input)Create a folder in a bucket.
getUploadUrl(input)Get a presigned URL for uploading an object.
getDownloadUrl(params)Get a presigned URL for downloading an object.
deleteObject(params)Delete an object or folder from a bucket.

List buckets

const { data: buckets } = await r.storage.listBuckets({
project_id: 'proj_123',
});
for (const bucket of buckets) {
console.log(`${bucket.name} — created ${bucket.created_at}`);
}

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project to list buckets for.

Get a bucket

const { data: bucket } = await r.storage.getBucket('uploads', {
project_id: 'proj_123',
});
console.log(bucket.id);
console.log(bucket.name);

Create a bucket

const { data: bucket } = await r.storage.createBucket({
project_id: 'proj_123',
name: 'uploads',
});
console.log(bucket.id);
console.log(bucket.name);

Input fields:

FieldTypeRequiredDescription
project_idstringYesProject to create the bucket in.
namestringYesBucket name. Must be unique within the project.

Ensure a bucket (idempotent)

Creates the bucket if it does not exist, or returns the existing one. Use this in deployment scripts.

const { data: bucket } = await r.storage.ensureBucket({
project_id: 'proj_123',
name: 'uploads',
});
console.log(bucket.name); // 'uploads'

ensureBucket is idempotent. Calling it multiple times with the same project and name will not create duplicates.

List items

const { data: items } = await r.storage.listItems({
project_id: 'proj_123',
bucket_name: 'uploads',
});
for (const item of items) {
console.log(`${item.key} — ${item.size} bytes — ${item.last_modified}`);
}

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project that owns the bucket.
bucket_namestringYesBucket name.
prefixstring?NoFilter by key prefix (e.g., 'images/').
limitnumber?NoMax results to return.

Returns: SingleResponse<StorageBucketItem[]>

interface StorageBucketItem {
key: string;
size: number;
last_modified: string;
etag?: string;
}

Create a folder

await r.storage.createFolder({
project_id: 'proj_123',
bucket_name: 'uploads',
folder_path: 'images/avatars',
});

Upload a file (presigned URL)

Uploads use a two-step process: first get a presigned URL from the API, then PUT the file directly to R2.

// 1. Get a presigned upload URL
const { data: { url } } = await r.storage.getUploadUrl({
project_id: 'proj_123',
bucket_name: 'uploads',
key: 'images/logo.png',
content_type: 'image/png',
});
// 2. Upload the file directly to the presigned URL
const fileBuffer = await fs.promises.readFile('./logo.png');
await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'image/png' },
body: fileBuffer,
});
console.log('Upload complete!');

Input fields:

FieldTypeRequiredDescription
project_idstringYesThe project that owns the bucket.
bucket_namestringYesBucket name.
keystringYesObject key (path within the bucket).
content_typestring?NoMIME type (e.g., 'image/png', 'application/pdf').

Download a file (presigned URL)

// 1. Get a presigned download URL
const { data: { url } } = await r.storage.getDownloadUrl({
project_id: 'proj_123',
bucket_name: 'uploads',
key: 'images/logo.png',
});
// 2. Download the file
const response = await fetch(url);
const buffer = await response.arrayBuffer();
// Or redirect the user to the URL in a web app
console.log('Download URL:', url);

Delete an object

await r.storage.deleteObject({
project_id: 'proj_123',
bucket_name: 'uploads',
key: 'images/old-logo.png',
});

Full example: upload and download flow

import { Recursiv } from '@recursiv/sdk';
import fs from 'node:fs';
const r = new Recursiv();
const PROJECT_ID = 'proj_123';
const BUCKET = 'user-uploads';
// 1. Ensure the bucket exists
await r.storage.ensureBucket({
project_id: PROJECT_ID,
name: BUCKET,
});
// 2. Create a folder structure
await r.storage.createFolder({
project_id: PROJECT_ID,
bucket_name: BUCKET,
folder_path: 'avatars',
});
// 3. Upload a file
const { data: upload } = await r.storage.getUploadUrl({
project_id: PROJECT_ID,
bucket_name: BUCKET,
key: 'avatars/user_123.jpg',
content_type: 'image/jpeg',
});
const file = await fs.promises.readFile('./avatar.jpg');
await fetch(upload.url, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' },
body: file,
});
// 4. List uploaded files
const { data: items } = await r.storage.listItems({
project_id: PROJECT_ID,
bucket_name: BUCKET,
prefix: 'avatars/',
});
console.log('Files in avatars/:', items.map((i) => i.key));
// 5. Get a download URL to share
const { data: download } = await r.storage.getDownloadUrl({
project_id: PROJECT_ID,
bucket_name: BUCKET,
key: 'avatars/user_123.jpg',
});
console.log('Download URL:', download.url);
// 6. Clean up
await r.storage.deleteObject({
project_id: PROJECT_ID,
bucket_name: BUCKET,
key: 'avatars/user_123.jpg',
});