File Storage
Files an app keeps at runtime (uploads, attachments, generated PDFs and exports) do not survive on Vercel or Cloud Run: the disk is temporary. Telbase hosts file storage for them: one private, S3-compatible bucket per app.
How It Works
Your agent creates the app's bucket with telbase storage create <app>. The answer names the bucket and its four values:
| Variable | Purpose |
|---|---|
R2_ENDPOINT | S3-compatible endpoint URL |
R2_ACCESS_KEY_ID | Access key for authentication |
R2_SECRET_ACCESS_KEY | Secret key for authentication |
R2_BUCKET_NAME | Name of the storage bucket |
The agent sets each value on the provider from a command, never through a file, under the names your code reads. The region is auto. The bucket is S3-compatible, so standard AWS SDK patterns work.
Setting the values on the app
telbase storage create my-app
# Vercel: one value at a time, the same for the other three
telbase storage credentials my-app --get R2_SECRET_ACCESS_KEY | telbase vercel env add R2_SECRET_ACCESS_KEY production
# Cloud Run: on the deploy command
--update-env-vars "R2_ENDPOINT=$(telbase storage credentials my-app --get R2_ENDPOINT),..."Seeds and scripts that write to the bucket run with the values set and nothing written to disk: telbase storage run my-app -- npm run seed. The key reaches this bucket's files only; it cannot create or delete buckets.
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:
npm install @aws-sdk/client-s3Create a storage client:
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:
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
Run a local command with the bucket's values set:
telbase storage run my-app -- npm run devYour local code connects to the same bucket as production. This means files uploaded locally are visible in production and vice versa.
my-app-dev) with its own storage bucket.Managing Credentials
# View credentials (masked)
telbase storage credentials my-app
# Rotate: the old key stops at once
telbase storage credentials my-app --rotate --yesCORS Configuration
If the browser uploads straight to the bucket (presigned PUT URLs, needed for files over 4.5 MB on Vercel), allow the page's origin: scheme and host exactly, no trailing slash.
telbase storage cors add my-app https://my-app.telbase.aiStorage Tiers
| Tier | Buckets | Storage | Cost |
|---|---|---|---|
| Free | — | — | $0 |
| Starter | 1 | 1 GB | $5/mo |
| Builder | 3 | 5 GB | $19/mo |
| Pro | 10 | 25 GB | $79/mo |
Storage uses Cloudflare R2 with $0 egress fees. Upgrade at Settings → Billing.
Next Steps
- Quick Start — deploy your first app
- Databases — SQLite and PostgreSQL
- Using with Claude Code — the skills and the MCP server
- CLI Reference — full command reference