Storage
DYPAI provides file storage powered by Cloudflare R2 (S3-compatible). Each project can create multiple buckets to organize files, with public or private access. Uploads go directly to R2 using presigned URLs, and private files are served through signed URLs via a Cloudflare Worker.
Key Concepts
- Buckets — top-level containers for organizing files. Each bucket is either public (served via CDN, no auth required) or private (requires a signed URL).
- Presigned Uploads — the SDK gets a presigned URL from the engine, then uploads the file directly to R2. No file data passes through your server.
- Signed URLs — time-limited URLs for downloading private files, verified by a Cloudflare Worker.
- File paths — files are organized by entity ID and folder, e.g.
{entity_id}/image/avatars/photo.jpg.
Buckets
Buckets are stored in the storage.buckets table. Each bucket has:
| Field | Description |
|---|---|
name | Unique bucket identifier (e.g. "avatars", "documents") |
public | true for public access, false for private (signed URLs required) |
provider | Storage provider (always "r2") |
You can create buckets from the Storage section in your project dashboard.
Public bucket files are served directly via CDN with no authentication. Private bucket files always require a signed URL.
Upload Flow
DYPAI uses a 3-phase upload that sends files directly to Cloudflare R2, keeping your server lightweight:
1. SIGN → Client requests a presigned upload URL from the engine
2. UPLOAD → Client uploads the file directly to R2 using the presigned URL
3. VERIFY → Client confirms the upload, engine records metadata in the database
Using the SDK
All file operations go through your endpoints, which use the dypai_storage node in the backend:
const { data, error } = await dypai.api.upload('storage_files', file, {
params: {
operation: 'upload',
file_path: `invoices/${file.name}`,
},
onProgress: (pct) => console.log(`Upload: ${pct}%`),
});
if (data) {
console.log('Storage path:', data.storage_path);
}
The SDK handles the entire 3-phase flow automatically. The onProgress callback provides real-time upload progress since the file goes directly from the browser to R2.
Using the React Hook
import { useUpload } from '@dypai-ai/client-sdk/react';
function FileUploader() {
const { upload, progress, isUploading } = useUpload('storage_files');
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const { data, error } = await upload(file, {
operation: 'upload',
file_path: `avatars/${file.name}`,
});
};
return (
<div>
<input type="file" onChange={handleFile} disabled={isUploading} />
{isUploading && <p>Uploading: {progress}%</p>}
</div>
);
}
Important: use storage_path
During upload the backend may append a UUID or normalize the filename. Save storage_path in your database, not the original file_path.
Downloading Files
Signed URL Download
For secure downloads, call a backend endpoint that validates ownership and returns the signed URL:
const { data } = await dypai.api.post('download_file', {
entity_id: invoice.id,
});
const signedUrl = data?.signedUrl || data?.signed_url;
Direct Download
await dypai.api.download('download_file', { entity_id: invoice.id }, {
fileName: 'invoice.pdf',
});
Listing Files
const { data } = await dypai.api.post('storage_files', {
operation: 'list',
prefix: 'invoices/2024',
});
Deleting Files
const { error } = await dypai.api.delete('storage_delete_files', {
params: { file_path: storedPath },
});
Deleted files are permanently removed from R2. This action cannot be undone.
Storage limits & usage
Object storage is metered at the workspace (organization) level: the limit comes from your org plan and is shared across every app in the workspace. When you exceed the object-storage quota, new uploads are blocked until you free up space or upgrade.
Track your consumption from the per-project Usage page (Observe → Usage), which shows Object storage alongside Build minutes and Database storage.
See Usage & Quotas for the full breakdown of what counts toward your plan limits.
File Size Limits
Maximum file size depends on the file type. These caps are approximate and may change — treat them as guidance:
| File Type | Max Size |
|---|---|
| Image (png, jpg, webp, etc.) | 25 MB |
| Document (pdf, docx, etc.) | 50 MB |
| Spreadsheet (xlsx, csv, etc.) | 25 MB |
| Presentation (pptx, etc.) | 100 MB |
| Audio (mp3, wav, etc.) | 250 MB |
| Video (mp4, mov, etc.) | 1 GB |
| Archive (zip, tar, etc.) | 500 MB |
| Other | 10 MB |
Blocked File Types
For security, the following file extensions are blocked from upload:
- Executables:
.exe,.msi,.dmg,.pkg,.deb,.rpm - Scripts:
.bat,.cmd,.sh,.ps1,.vbs,.scr - Office with macros:
.xlsm,.pptm,.docm - Java:
.jar,.class - Apps:
.app,.ipa,.apk
Image Generation writes to storage
The Image Generation AI node saves its output to your storage and returns a storage URL you can serve or save in the database — no manual upload needed.
Next Steps
- SDK Storage Reference — upload, download, and file management from the SDK.
- API Builder — create custom file upload/download workflows.
- Usage & Quotas — track object storage against your plan limit.