File Storage
Upload files as part of your documents and user profiles with automatic S3 storage integration. Files are uploaded alongside your data and their URLs are automatically stored in your documents.Cocobase integrates file uploads directly into document and user operations. Files are automatically uploaded to S3 storage and their URLs are saved in your documents.
Overview
File storage features:- Integrated uploads - Upload files with document creation/updates
- Multiple file types - Images, documents, videos, any file type
- Single or array - Upload single files or arrays of files
- Automatic URLs - File URLs automatically saved in documents
- S3 storage - Secure, scalable cloud storage
- User profile files - Upload avatars and cover photos with registration
Upload Files with Documents
Upload files when creating or updating documents using field naming.Single File Upload
- JavaScript
- TypeScript
- Dart
- Python
Copy
import { Cocobase } from 'cocobase';
const db = new Cocobase({ apiKey: 'your-api-key' });
// Upload avatar with user profile
const user = await db.createDocumentWithFiles(
'users',
{
name: 'John Doe',
email: '[email protected]',
bio: 'Software developer'
},
{
avatar: avatarFile, // Single file
cover_photo: coverPhotoFile // Another single file
}
);
console.log('Avatar URL:', user.data.avatar);
console.log('Cover URL:', user.data.cover_photo);
Copy
import { Cocobase } from 'cocobase';
interface UserData {
name: string;
email: string;
bio: string;
}
const db = new Cocobase({ apiKey: 'your-api-key' });
// Type-safe file upload
const user = await db.createDocumentWithFiles<UserData>(
'users',
{
name: 'John Doe',
email: '[email protected]',
bio: 'Software developer'
},
{
avatar: avatarFile,
cover_photo: coverPhotoFile
}
);
// user.data.avatar is a string URL
console.log('Avatar URL:', user.data.avatar);
Copy
import 'package:cocobase_flutter/cocobase_flutter.dart';
import 'dart:io';
final db = Cocobase(CocobaseConfig(apiKey: 'your-api-key'));
// Get file from file picker
final avatarFile = File('/path/to/avatar.jpg');
final coverFile = File('/path/to/cover.jpg');
// Upload files with document
final user = await db.createDocumentWithFiles(
'users',
{
'name': 'John Doe',
'email': '[email protected]',
'bio': 'Software developer'
},
{
'avatar': avatarFile,
'cover_photo': coverFile
}
);
print('Avatar URL: ${user.data['avatar']}');
print('Cover URL: ${user.data['cover_photo']}');
Copy
from cocobase import Cocobase
db = Cocobase(api_key='your-api-key')
# Upload file with document
with open('/path/to/avatar.jpg', 'rb') as avatar_file, \
open('/path/to/cover.jpg', 'rb') as cover_file:
user = db.create_document_with_files(
'users',
{
'name': 'John Doe',
'email': '[email protected]',
'bio': 'Software developer'
},
{
'avatar': avatar_file,
'cover_photo': cover_file
}
)
print(f"Avatar URL: {user['data']['avatar']}")
print(f"Cover URL: {user['data']['cover_photo']}")
Copy
{
"id": "user-123",
"collection": "users",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"data": {
"name": "John Doe",
"email": "[email protected]",
"bio": "Software developer",
"avatar": "https://storage.cocobase.buzz/.../avatar.jpg",
"cover_photo": "https://storage.cocobase.buzz/.../cover.jpg"
}
}
Multiple Files (Array)
Upload multiple files to create an array of URLs:- JavaScript
- TypeScript
- Dart
- Python
Copy
// Product with gallery
const product = await db.createDocumentWithFiles(
'products',
{
name: 'Laptop',
price: 1299,
description: 'High-performance laptop'
},
{
main_image: mainImageFile,
gallery: [img1File, img2File, img3File] // Array of files
}
);
console.log('Main image:', product.data.main_image);
console.log('Gallery:', product.data.gallery);
// Gallery: ["https://.../img1.jpg", "https://.../img2.jpg", "https://.../img3.jpg"]
Copy
interface ProductData {
name: string;
price: number;
description: string;
}
const product = await db.createDocumentWithFiles<ProductData>(
'products',
{
name: 'Laptop',
price: 1299,
description: 'High-performance laptop'
},
{
main_image: mainImageFile,
gallery: [img1File, img2File, img3File]
}
);
// Type-safe access
const imageUrl: string = product.data.main_image;
const galleryUrls: string[] = product.data.gallery;
Copy
// Product with multiple images
final images = [
File('/path/to/img1.jpg'),
File('/path/to/img2.jpg'),
File('/path/to/img3.jpg')
];
final product = await db.createDocumentWithFiles(
'products',
{
'name': 'Laptop',
'price': 1299,
'description': 'High-performance laptop'
},
{
'main_image': File('/path/to/main.jpg'),
'gallery': images
}
);
print('Gallery URLs: ${product.data['gallery']}');
Copy
# Product with gallery
with open('/path/to/main.jpg', 'rb') as main, \
open('/path/to/img1.jpg', 'rb') as img1, \
open('/path/to/img2.jpg', 'rb') as img2, \
open('/path/to/img3.jpg', 'rb') as img3:
product = db.create_document_with_files(
'products',
{
'name': 'Laptop',
'price': 1299,
'description': 'High-performance laptop'
},
{
'main_image': main,
'gallery': [img1, img2, img3]
}
)
print(f"Gallery: {product['data']['gallery']}")
Copy
{
"id": "product-123",
"data": {
"name": "Laptop",
"price": 1299,
"description": "High-performance laptop",
"main_image": "https://storage.cocobase.buzz/.../main.jpg",
"gallery": [
"https://storage.cocobase.buzz/.../img1.jpg",
"https://storage.cocobase.buzz/.../img2.jpg",
"https://storage.cocobase.buzz/.../img3.jpg"
]
}
}
Update Documents with Files
Update existing documents with new files:- JavaScript
- TypeScript
- Dart
- Python
Copy
// Update only the avatar
await db.updateDocumentWithFiles(
'users',
'user-123',
undefined, // No data changes
{
avatar: newAvatarFile
}
);
// Update both data and files
await db.updateDocumentWithFiles(
'users',
'user-123',
{ bio: 'Updated bio' }, // Data changes
{
avatar: newAvatarFile,
cover_photo: newCoverFile
}
);
Copy
// Type-safe update
await db.updateDocumentWithFiles<UserData>(
'users',
'user-123',
{ bio: 'Updated bio' },
{ avatar: newAvatarFile }
);
Copy
// Update files only
await db.updateDocumentWithFiles(
'users',
'user-123',
null, // No data updates
{
'avatar': File('/path/to/new-avatar.jpg')
}
);
// Update data and files
await db.updateDocumentWithFiles(
'users',
'user-123',
{'bio': 'Updated bio'},
{'avatar': File('/path/to/new-avatar.jpg')}
);
Copy
# Update files
with open('/path/to/new-avatar.jpg', 'rb') as avatar:
db.update_document_with_files(
'users',
'user-123',
None, # No data updates
{'avatar': avatar}
)
# Update data and files
with open('/path/to/new-avatar.jpg', 'rb') as avatar:
db.update_document_with_files(
'users',
'user-123',
{'bio': 'Updated bio'},
{'avatar': avatar}
)
User Registration with Files
Register users with profile pictures:- JavaScript
- TypeScript
- Dart
- Python
Copy
// Register with avatar
await db.registerWithFiles(
'[email protected]',
'password123',
{
username: 'johndoe',
full_name: 'John Doe',
bio: 'Developer'
},
{
avatar: avatarFile
}
);
// Get user details
const user = await db.auth.getUser();
console.log('Avatar URL:', user.avatar);
Copy
interface UserMetadata {
username: string;
full_name: string;
bio?: string;
}
// Type-safe registration
await db.registerWithFiles<UserMetadata>(
'[email protected]',
'password123',
{
username: 'johndoe',
full_name: 'John Doe',
bio: 'Developer'
},
{
avatar: avatarFile,
cover_photo: coverFile
}
);
const user = await db.auth.getUser();
// user.avatar is a string URL
Copy
// Register with files
await db.auth.registerWithFiles(
email: '[email protected]',
password: 'password123',
data: {
'username': 'johndoe',
'full_name': 'John Doe',
'bio': 'Developer'
},
files: {
'avatar': File('/path/to/avatar.jpg')
}
);
// Get user
final user = await db.auth.getUser();
print('Avatar: ${user.avatar}');
Copy
# Register with avatar
with open('/path/to/avatar.jpg', 'rb') as avatar:
db.auth.register_with_files(
'[email protected]',
'password123',
{
'username': 'johndoe',
'full_name': 'John Doe',
'bio': 'Developer'
},
{
'avatar': avatar
}
)
# Get user
user = db.auth.get_user()
print(f"Avatar: {user['avatar']}")
Copy
{
"id": "user-123",
"email": "[email protected]",
"username": "johndoe",
"full_name": "John Doe",
"bio": "Developer",
"avatar": "https://storage.cocobase.buzz/.../avatar.jpg",
"createdAt": "2024-01-15T10:00:00Z"
}
Update User Profile with Files
Update authenticated user’s profile pictures:- JavaScript
- TypeScript
- Dart
- Python
Copy
// Update only avatar
await db.updateUserWithFiles(
undefined,
undefined,
undefined,
{ avatar: newAvatarFile }
);
// Update bio and avatar
await db.updateUserWithFiles(
{ bio: 'Updated bio', location: 'New York' },
undefined,
undefined,
{ avatar: newAvatarFile }
);
// Update email, data, and files
await db.updateUserWithFiles(
{ username: 'newusername' },
'[email protected]',
undefined,
{
avatar: newAvatar,
cover_photo: newCover
}
);
Copy
// Type-safe user update
await db.updateUserWithFiles<UserMetadata>(
{ bio: 'Updated bio' },
undefined,
undefined,
{ avatar: newAvatarFile }
);
Copy
// Update user avatar
await db.auth.updateUserWithFiles(
data: {'bio': 'Updated bio'},
files: {'avatar': File('/path/to/new-avatar.jpg')}
);
// Update multiple fields
await db.auth.updateUserWithFiles(
data: {'bio': 'Updated bio', 'location': 'New York'},
email: '[email protected]',
files: {
'avatar': File('/path/to/avatar.jpg'),
'cover_photo': File('/path/to/cover.jpg')
}
);
Copy
# Update user avatar
with open('/path/to/new-avatar.jpg', 'rb') as avatar:
db.auth.update_user_with_files(
data={'bio': 'Updated bio'},
files={'avatar': avatar}
)
# Update multiple fields
with open('/path/to/avatar.jpg', 'rb') as avatar, \
open('/path/to/cover.jpg', 'rb') as cover:
db.auth.update_user_with_files(
data={'bio': 'Updated', 'location': 'New York'},
email='[email protected]',
files={
'avatar': avatar,
'cover_photo': cover
}
)
React Example
Complete React component with file uploads:Copy
import React, { useState } from 'react';
import { Cocobase } from 'cocobase';
const db = new Cocobase({ apiKey: 'your-api-key' });
function SignUpForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [username, setUsername] = useState('');
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
// Validate file
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
alert('Please upload a valid image file');
return;
}
if (file.size > 5 * 1024 * 1024) { // 5MB
alert('File size must be less than 5MB');
return;
}
setAvatarFile(file);
// Show preview
const reader = new FileReader();
reader.onloadend = () => {
setAvatarPreview(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
await db.registerWithFiles(
email,
password,
{ username },
avatarFile ? { avatar: avatarFile } : undefined
);
// Get user details
const user = await db.auth.getUser();
console.log('User created:', user);
alert('Sign up successful!');
} catch (error) {
console.error('Sign up failed:', error);
alert('Sign up failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Create Your Account</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Avatar Preview */}
{avatarPreview && (
<div className="flex justify-center mb-4">
<img
src={avatarPreview}
alt="Avatar preview"
className="w-24 h-24 rounded-full object-cover"
/>
</div>
)}
{/* Avatar Upload */}
<div>
<label className="block text-sm font-medium mb-2">
Profile Picture
</label>
<input
type="file"
accept="image/*"
onChange={handleAvatarChange}
className="w-full"
/>
</div>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
className="w-full px-3 py-2 border rounded"
/>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 border rounded"
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full px-3 py-2 border rounded"
/>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
>
{loading ? 'Creating Account...' : 'Sign Up'}
</button>
</form>
</div>
);
}
Product Gallery Example
Create products with multiple images:Copy
import React, { useState } from 'react';
import { Cocobase } from 'cocobase';
const db = new Cocobase({ apiKey: 'your-api-key' });
function CreateProductForm() {
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [mainImage, setMainImage] = useState<File | null>(null);
const [galleryImages, setGalleryImages] = useState<File[]>([]);
const [loading, setLoading] = useState(false);
const handleGalleryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
setGalleryImages(files);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const files: Record<string, File | File[]> = {};
if (mainImage) {
files.main_image = mainImage;
}
if (galleryImages.length > 0) {
files.gallery = galleryImages;
}
const product = await db.createDocumentWithFiles(
'products',
{
name,
price: parseFloat(price),
status: 'active'
},
files
);
console.log('Product created:', product);
alert('Product created successfully!');
// Reset form
setName('');
setPrice('');
setMainImage(null);
setGalleryImages([]);
} catch (error) {
console.error('Product creation failed:', error);
alert('Failed to create product');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<h2 className="text-2xl font-bold">Create Product</h2>
<input
type="text"
placeholder="Product Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full px-3 py-2 border rounded"
/>
<input
type="number"
placeholder="Price"
value={price}
onChange={(e) => setPrice(e.target.value)}
required
step="0.01"
className="w-full px-3 py-2 border rounded"
/>
<div>
<label className="block text-sm font-medium mb-2">Main Image</label>
<input
type="file"
accept="image/*"
onChange={(e) => setMainImage(e.target.files?.[0] || null)}
className="w-full"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">
Gallery Images (Multiple)
</label>
<input
type="file"
accept="image/*"
multiple
onChange={handleGalleryChange}
className="w-full"
/>
{galleryImages.length > 0 && (
<p className="text-sm text-gray-600 mt-1">
{galleryImages.length} image(s) selected
</p>
)}
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
>
{loading ? 'Creating...' : 'Create Product'}
</button>
</form>
);
}
Best Practices
Validate Files Before Upload
Validate Files Before Upload
Always validate file types and sizes before uploading:
Copy
const validateImage = (file: File): boolean => {
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
const maxSize = 5 * 1024 * 1024; // 5MB
if (!validTypes.includes(file.type)) {
alert('Please upload a valid image file');
return false;
}
if (file.size > maxSize) {
alert('File size must be less than 5MB');
return false;
}
return true;
};
Show Upload Progress
Show Upload Progress
Provide visual feedback during uploads:
Copy
const [loading, setLoading] = useState(false);
const handleUpload = async () => {
setLoading(true);
try {
await db.createDocumentWithFiles(/* ... */);
} finally {
setLoading(false);
}
};
Compress Images
Compress Images
Reduce file sizes before uploading:
Copy
import imageCompression from 'browser-image-compression';
const compressImage = async (file: File): Promise<File> => {
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true
};
return await imageCompression(file, options);
};
// Use compressed image
const compressed = await compressImage(originalFile);
await db.createDocumentWithFiles('users', data, { avatar: compressed });
Show Image Previews
Show Image Previews
Let users preview images before uploading:
Copy
const [preview, setPreview] = useState<string | null>(null);
const handleFileChange = (file: File) => {
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result as string);
};
reader.readAsDataURL(file);
};
Handle Errors Gracefully
Handle Errors Gracefully
Provide meaningful error messages:
Copy
try {
await db.createDocumentWithFiles(/* ... */);
} catch (error) {
if (error.message.includes('Storage limit')) {
alert('Storage limit exceeded. Please upgrade your plan.');
} else if (error.message.includes('Invalid file')) {
alert('Invalid file format. Please use JPG, PNG, or WebP.');
} else {
alert('Upload failed. Please try again.');
}
}
Field Naming Convention
Use descriptive field names for your files:Copy
// User profiles
{ avatar: file, cover_photo: file, profile_picture: file }
// Products
{ main_image: file, thumbnail: file, gallery: [files] }
// Blog posts
{ featured_image: file, inline_images: [files] }
// Documents
{ id_document: file, verification_photo: file }
// Real estate
{ main_photo: file, photos: [files], floor_plan: file }
File Limits
| Limit Type | Value |
|---|---|
| Maximum file size | 50 MB |
| Supported formats | All file types |
| Files per request | 10 files |
| Storage per project | Based on plan |
Common Use Cases
User Avatars
Copy
await db.registerWithFiles(
email,
password,
{ username },
{ avatar: avatarFile }
);
Product Images
Copy
await db.createDocumentWithFiles(
'products',
{ name, price },
{ main_image: mainFile, gallery: [img1, img2, img3] }
);
Document Uploads
Copy
await db.createDocumentWithFiles(
'applications',
{ applicant_name, status },
{ resume: resumePdf, cover_letter: coverPdf }
);
Blog Post Media
Copy
await db.createDocumentWithFiles(
'posts',
{ title, content },
{ featured_image: featuredFile, attachments: [file1, file2] }
);
