Upload & Download
Upload and download files using the ZendBX Storage SDK or REST API. Works in both browser and Node.js environments.
Uploading Files
Browser Upload
typescript
// Browser - Upload from file input
const bucket = db.storage.bucket('documents');
const file = event.target.files[0];
const { data, error } = await bucket.upload(file);
if (error) {
console.error('Upload failed:', error);
} else {
console.log('Uploaded:', data);
// {
// id: "file-uuid",
// filename: "report.pdf",
// size: 245760,
// content_type: "application/pdf",
// created_at: "2024-01-15T10:30:00Z",
// url: "/p/demo/storage/files/{id}/download"
// }
}Node.js Upload
typescript
// Node.js - Upload from filesystem
import fs from 'fs';
const bucket = db.storage.bucket('documents');
const buffer = fs.readFileSync('./report.pdf');
const { data, error } = await bucket.upload(buffer, 'report.pdf', {
contentType: 'application/pdf',
});Custom Filenames
typescript
// Custom filename
const { data, error } = await bucket.upload(file, 'custom-name.pdf');
// Auto-generate unique filename
const timestamp = Date.now();
const { data, error } = await bucket.upload(file, `document-${timestamp}.pdf`);💡Filenames are not required to be unique. The backend generates unique IDs for each file.
Upload Options
typescript
// Upload with metadata
const { data, error } = await bucket.upload(file, 'resume.pdf', {
contentType: 'application/pdf',
metadata: {
userId: '123',
uploadedBy: 'john@example.com',
category: 'resumes',
},
});| Parameter | Type | Required | Description |
|---|---|---|---|
| file | File | Blob | Buffer | required | File to upload |
| filename | string | optional | Custom filename (defaults to file.name) |
| contentType | string | optional | MIME type (auto-detected for File objects) |
| metadata | object | optional | Custom metadata (JSON object) |
Upload Progress
typescript
// Track upload progress (coming soon)
const { data, error } = await bucket.upload(file, {
onProgress: (progress) => {
console.log(`Uploaded: ${progress.loaded} / ${progress.total} bytes`);
console.log(`Progress: ${Math.round(progress.percentage)}%`);
},
});💡Progress tracking is planned for a future release. Currently uploads complete without progress callbacks.
Downloading Files
Browser Download
typescript
// Download file
const bucket = db.storage.bucket('documents');
const response = await bucket.download('file-id');
// Get as Blob (browser)
const blob = await response.blob();
const url = URL.createObjectURL(blob);
// Use in <img> or <a> tag
document.querySelector('img').src = url;
// Or trigger download
const a = document.createElement('a');
a.href = url;
a.download = 'filename.pdf';
a.click();Node.js Download
typescript
// Download as Buffer (Node.js)
const response = await bucket.download('file-id');
const buffer = await response.buffer();
// Save to file
fs.writeFileSync('./downloaded.pdf', buffer);Temporary URLs
Signed URLs provide temporary access to private files without authentication:
typescript
// Generate temporary download URL
const bucket = db.storage.bucket('documents');
const { data, error } = await bucket.createSignedUrl('file-id', '1h');
console.log(data.url);
// https://api.zendbx.in/p/demo/storage/signed/abc123...
// Use in <img> tag
<img src={data.url} alt="Document" />
// Available expiry options
'5m' // 5 minutes
'15m' // 15 minutes
'1h' // 1 hour
'24h' // 24 hours
'7d' // 7 days💡Signed URLs expire after the specified duration. Generate a new URL when the old one expires.
Public URLs
typescript
// Public bucket - direct preview URL
const bucket = db.storage.bucket('public-assets');
const url = bucket.getPreviewUrl('logo.png');
// Use directly (no auth required)
<img src={url} alt="Logo" />⚠️Public URLs only work for files in public buckets. Private bucket files require signed URLs.
Listing Files
typescript
// List all files
const bucket = db.storage.bucket('documents');
const { data: files, error } = await bucket.list();
files.forEach(file => {
console.log(`${file.filename} - ${file.size} bytes`);
});Search & Filter
typescript
// Search and filter
const { data: files } = await bucket.list({
search: 'invoice', // Search filename
sortBy: 'created_at', // created_at | size | filename
sortDir: 'desc', // asc | desc
limit: 50, // Results per page
offset: 0, // Pagination offset
});Deleting Files
typescript
// Delete single file
const bucket = db.storage.bucket('documents');
const { error } = await bucket.delete('file-id');
// Delete multiple files
const { error } = await bucket.bulkDelete([
'file-id-1',
'file-id-2',
'file-id-3',
]);⚠️File deletion is permanent. Deleted files cannot be recovered unless you have backups enabled.
REST API Examples
Upload via cURL
bash
curl -X POST https://api.zendbx.in/p/demo/storage/buckets/documents/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "apikey: YOUR_ANON_KEY" \
-F "file=@/path/to/document.pdf"Download via cURL
bash
curl https://api.zendbx.in/p/demo/storage/files/{file-id}/download \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "apikey: YOUR_ANON_KEY" \
-o downloaded.pdfSecurity Best Practices
typescript
// Client-side: validate file before upload
function validateFile(file) {
const maxSize = 5 * 1024 * 1024; // 5 MB
const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
if (file.size > maxSize) {
throw new Error('File too large');
}
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type');
}
return true;
}
// Server-side: RLS policy
CREATE POLICY "Users can only upload to own folder"
ON storage_objects
FOR INSERT
WITH CHECK (
auth.uid()::text = (metadata->>'userId')
);- Validate files client-side: Check size and type before upload
- Use RLS policies: Restrict uploads to authorized users
- Never trust client input: Server-side validation is required
- Scan for malware: Implement virus scanning for user uploads
- Use private buckets: Default to private unless files are truly public
Error Handling
typescript
try {
const { data, error } = await bucket.upload(file);
if (error) {
if (error.code === 'file_too_large') {
alert('File exceeds size limit');
} else if (error.code === 'invalid_file_type') {
alert('File type not allowed');
} else if (error.code === 'quota_exceeded') {
alert('Storage quota exceeded');
} else {
alert('Upload failed: ' + error.message);
}
} else {
console.log('Upload successful:', data);
}
} catch (err) {
console.error('Unexpected error:', err);
}Common Error Codes
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_too_large | 413 | optional | File exceeds maximum size limit |
| invalid_file_type | 415 | optional | File type not allowed |
| quota_exceeded | 413 | optional | Storage quota exceeded |
| bucket_not_found | 404 | optional | Bucket does not exist |
| file_not_found | 404 | optional | File does not exist |
| permission_denied | 403 | optional | No access to bucket or file |
File Limits
| Parameter | Type | Required | Description |
|---|---|---|---|
| Max file size | string | optional | 5 GB (configurable per project) |
| Max filename length | number | optional | 255 characters |
| Supported file types | string | optional | All types (configurable per bucket) |
| Max files per bucket | string | optional | Unlimited |
Best Practices
- Use unique filenames: Append timestamps or UUIDs to avoid conflicts
- Set appropriate content types: Helps browsers handle files correctly
- Delete unused files: Clean up to avoid quota limits
- Use signed URLs for sensitive files: Limit access duration
- Implement progress indicators: Improves UX for large uploads
- Handle errors gracefully: Show user-friendly error messages

