File Storage

Telbase provisions S3-compatible file storage automatically when you deploy with --storage. No buckets to create, no credentials to copy — just deploy and start uploading.

How It Works

When you deploy with --storage, Telbase creates an R2 storage bucket and injects four environment variables into your app:

VariablePurpose
R2_ENDPOINTS3-compatible endpoint URL
R2_ACCESS_KEY_IDAccess key for authentication
R2_SECRET_ACCESS_KEYSecret key for authentication
R2_BUCKET_NAMEName of the storage bucket

These are available at process.env.* in your deployed app. The bucket is S3-compatible, so standard AWS SDK patterns work out of the box.

Auto-detection
Telbase auto-detects storage needs if your project uses multer, formidable, busboy, express-fileupload, @aws-sdk/client-s3, aws-sdk, boto3, or python-multipart. You can also provision explicitly with --storage.

Provisioning Storage

bash
# Deploy with file storage
telbase deploy --storage

# Or with Claude Code / MCP
telbase deploy --local --json --auto --storage

Storage is provisioned once on first deploy. Subsequent deploys preserve the same bucket and credentials. Use --no-storage to skip storage provisioning.

Using Storage in Your App

Use the standard AWS S3 SDK. The storage is S3-compatible, so any library that works with S3 works with Telbase storage.

Install the AWS SDK:

bash
npm install @aws-sdk/client-s3

Create a storage client:

typescript
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  },
});

// Upload a file
export async function uploadFile(key: string, body: Buffer, contentType: string) {
  await s3.send(new PutObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: key,
    Body: body,
    ContentType: contentType,
  }));
  return key;
}

// Download a file
export async function getFile(key: string) {
  const response = await s3.send(new GetObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: key,
  }));
  return response.Body;
}

Example: File Upload Endpoint

A complete Express endpoint for handling file uploads with multer:

typescript
import express from 'express';
import multer from 'multer';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

const s3 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  },
});

app.post('/upload', upload.single('file'), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file' });

  const key = `uploads/${Date.now()}-${req.file.originalname}`;

  await s3.send(new PutObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: key,
    Body: req.file.buffer,
    ContentType: req.file.mimetype,
  }));

  res.json({ key, size: req.file.size });
});

Local Development

Pull storage credentials to your local environment for development:

bash
# Download all env vars (including R2_*) to .env.local
telbase env pull

# Or view them individually
telbase env get R2_ENDPOINT
telbase env get R2_BUCKET_NAME

Your local code connects to the same R2 bucket as production. This means files uploaded locally are visible in production and vice versa.

Separate dev bucket
For isolated development, create a second Telbase project (e.g., my-app-dev) with its own storage bucket. Use telbase env pull in that project to get separate credentials.

Managing Credentials

Storage credentials can be viewed and rotated from the dashboard or CLI.

bash
# View credentials (redacted by default)
telbase env list

# Rotate credentials (generates new keys, invalidates old ones)
# Use the dashboard Storage tab > Rotate Credentials
Credential rotation
Rotating credentials invalidates the old keys immediately. Your app will use the new keys on next deploy. Make sure to redeploy after rotation.

CORS Configuration

If your frontend uploads directly to storage (browser-side uploads), configure CORS origins from the dashboard Storage tab. Add your app's domain to allow cross-origin requests.

Storage Tiers

TierBucketsStorageCost
Free$0
Starter11 GB$5/mo
Builder35 GB$19/mo
Pro1025 GB$79/mo

Storage uses Cloudflare R2 with $0 egress fees. Upgrade at Settings → Billing.

Next Steps