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

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

Parameters:

ParameterTypeRequiredDescription
project_idstringYesThe project to list buckets for.

Get a bucket

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

Create a bucket

1const { data: bucket } = await r.storage.createBucket({
2 project_id: 'proj_123',
3 name: 'uploads',
4});
5
6console.log(bucket.id);
7console.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.

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

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

List items

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

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[]>

1interface StorageBucketItem {
2 key: string;
3 size: number;
4 last_modified: string;
5 etag?: string;
6}

Create a folder

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

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// 1. Get a presigned upload URL
2const { data: { url } } = await r.storage.getUploadUrl({
3 project_id: 'proj_123',
4 bucket_name: 'uploads',
5 key: 'images/logo.png',
6 content_type: 'image/png',
7});
8
9// 2. Upload the file directly to the presigned URL
10const fileBuffer = await fs.promises.readFile('./logo.png');
11
12await fetch(url, {
13 method: 'PUT',
14 headers: { 'Content-Type': 'image/png' },
15 body: fileBuffer,
16});
17
18console.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// 1. Get a presigned download URL
2const { data: { url } } = await r.storage.getDownloadUrl({
3 project_id: 'proj_123',
4 bucket_name: 'uploads',
5 key: 'images/logo.png',
6});
7
8// 2. Download the file
9const response = await fetch(url);
10const buffer = await response.arrayBuffer();
11
12// Or redirect the user to the URL in a web app
13console.log('Download URL:', url);

Delete an object

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

Full example: upload and download flow

1import { Recursiv } from '@recursiv/sdk';
2import fs from 'node:fs';
3
4const r = new Recursiv();
5
6const PROJECT_ID = 'proj_123';
7const BUCKET = 'user-uploads';
8
9// 1. Ensure the bucket exists
10await r.storage.ensureBucket({
11 project_id: PROJECT_ID,
12 name: BUCKET,
13});
14
15// 2. Create a folder structure
16await r.storage.createFolder({
17 project_id: PROJECT_ID,
18 bucket_name: BUCKET,
19 folder_path: 'avatars',
20});
21
22// 3. Upload a file
23const { data: upload } = await r.storage.getUploadUrl({
24 project_id: PROJECT_ID,
25 bucket_name: BUCKET,
26 key: 'avatars/user_123.jpg',
27 content_type: 'image/jpeg',
28});
29
30const file = await fs.promises.readFile('./avatar.jpg');
31await fetch(upload.url, {
32 method: 'PUT',
33 headers: { 'Content-Type': 'image/jpeg' },
34 body: file,
35});
36
37// 4. List uploaded files
38const { data: items } = await r.storage.listItems({
39 project_id: PROJECT_ID,
40 bucket_name: BUCKET,
41 prefix: 'avatars/',
42});
43
44console.log('Files in avatars/:', items.map((i) => i.key));
45
46// 5. Get a download URL to share
47const { data: download } = await r.storage.getDownloadUrl({
48 project_id: PROJECT_ID,
49 bucket_name: BUCKET,
50 key: 'avatars/user_123.jpg',
51});
52
53console.log('Download URL:', download.url);
54
55// 6. Clean up
56await r.storage.deleteObject({
57 project_id: PROJECT_ID,
58 bucket_name: BUCKET,
59 key: 'avatars/user_123.jpg',
60});