Storage
The Storage service provides object storage for files of any size. It supports direct server uploads, presigned URLs for browser-to-storage uploads, download URL generation, and file metadata. In development, the CLI stores files locally. In production, Maravilla Cloud handles everything. Your code works identically in both environments.
import { getPlatform } from '@maravilla-labs/platform';
const platform = getPlatform();
await platform.env.STORAGE.put('docs/report.pdf', pdfBuffer, {
contentType: 'application/pdf'
});
API Reference
put(key, data, metadata?)
Uploads a file from the server. Use this for small files or when the data is already available on the server side.
Parameters:
key(string) — unique file path/keydata(Uint8Array | ArrayBuffer) — the file contentmetadata(object, optional):contentType(string) — MIME typefilename(string) — original filenametags(object) — custom tags
await platform.env.STORAGE.put('uploads/photo.jpg', imageBuffer, {
contentType: 'image/jpeg',
filename: 'vacation.jpg',
tags: { uploadedBy: 'user-123' }
});
get(key)
Retrieves a file and its metadata.
Returns: { data: Uint8Array, metadata: { ... } }
const file = await platform.env.STORAGE.get('uploads/photo.jpg');
// file.data -- Uint8Array of the file content
// file.metadata -- associated metadata object
delete(key)
Deletes a file from storage.
await platform.env.STORAGE.delete('uploads/photo.jpg');
list(prefix?, limit?)
Lists files in storage, optionally filtered by prefix.
Parameters:
prefix(string, optional, default:'') — only return files whose keys start with this prefixlimit(number, optional, default: 100, max: 1000) — maximum number of results
const files = await platform.env.STORAGE.list('uploads/', 50);
getMetadata(key)
Returns only the metadata for a file, without downloading the file content. Useful for checking file details without transferring the data.
const metadata = await platform.env.STORAGE.getMetadata('uploads/photo.jpg');
// { contentType, filename, size, ... }
generateUploadUrl(key, contentType, size)
Generates a presigned URL that allows a client (typically a browser) to upload a file directly to storage, bypassing your server. This is the recommended approach for large files.
Parameters:
key(string) — the storage key the file will be saved undercontentType(string) — the MIME type of the file being uploadedsize(number) — maximum allowed file size in bytes
Returns: { url, method, headers }
const uploadUrl = await platform.env.STORAGE.generateUploadUrl(
'uploads/avatar.png',
'image/png',
5 * 1024 * 1024 // 5 MB max
);
// Return uploadUrl to the client for direct upload
// uploadUrl.url -- the presigned URL
// uploadUrl.method -- HTTP method to use (typically "PUT")
// uploadUrl.headers -- headers the client must include
generateDownloadUrl(key, expiresInSeconds?)
Generates a temporary presigned URL for downloading a file. Useful for serving private files to authenticated users without exposing your storage credentials.
Parameters:
key(string) — the file keyexpiresInSeconds(number, optional) — seconds until the URL expires (default: 900 / 15 minutes)
Returns: { url, method, headers, expiresIn }
const download = await platform.env.STORAGE.generateDownloadUrl(
'reports/q1.pdf',
3600 // 1 hour
);
// Redirect user to download.url
Upload Patterns
Direct Upload (Presigned URL) — Recommended
Best for large files. The browser uploads directly to storage, reducing server bandwidth and improving performance.
Step 1: Server generates the presigned URL
// Server endpoint (e.g., SvelteKit +server.ts)
import { json } from '@sveltejs/kit';
import { getPlatform } from '@maravilla-labs/platform';
export const POST = async ({ request }) => {
const platform = getPlatform();
const { key, content_type, size } = await request.json();
const uploadUrl = await platform.env.STORAGE.generateUploadUrl(
key,
content_type,
size || 10 * 1024 * 1024
);
return json({
upload_url: uploadUrl.url,
method: uploadUrl.method,
headers: uploadUrl.headers
});
};
Step 2: Client uploads directly to storage
async function uploadFile(file) {
const key = `uploads/${Date.now()}-${file.name}`;
// Get presigned URL from your server
const res = await fetch('/api/uploads/generate-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key,
content_type: file.type,
size_limit: file.size
})
});
const { upload_url, method, headers } = await res.json();
// Upload directly to storage
const uploadHeaders = new Headers();
if (headers) {
Object.entries(headers).forEach(([k, v]) => {
uploadHeaders.set(k, v);
});
}
await fetch(upload_url, {
method: method || 'PUT',
headers: uploadHeaders,
body: file
});
return key;
}
Server Upload
Best for small files or when you need to process the file before storing it (validation, resizing, transformation).
import { getPlatform } from '@maravilla-labs/platform';
export const POST = async ({ request }) => {
const platform = getPlatform();
const formData = await request.formData();
const file = formData.get('file');
const key = formData.get('key');
// Validate
if (file.size > 10 * 1024 * 1024) {
return json({ error: 'File too large' }, { status: 400 });
}
// Store with metadata
const metadata = {
filename: file.name,
content_type: file.type,
size: file.size,
uploaded_at: new Date().toISOString()
};
await platform.env.STORAGE.put(key, await file.arrayBuffer(), {
contentType: file.type,
filename: file.name,
metadata
});
return json({ success: true, key, metadata });
};
Combining Storage with KV for Metadata
Store file metadata in the KV Store for fast lookups without reading the file:
const platform = getPlatform();
// Upload file to storage
const key = `uploads/${crypto.randomUUID()}-${file.name}`;
await platform.env.STORAGE.put(key, fileData, {
contentType: file.type,
filename: file.name
});
// Store metadata in KV for quick access
await platform.env.KV.files.put(`metadata:${key}`, JSON.stringify({
filename: file.name,
contentType: file.type,
size: file.size,
uploadedAt: new Date().toISOString()
}));
Security Best Practices
File Validation
Always validate uploads on the server side, even when using presigned URLs:
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const MAX_SIZE = 10 * 1024 * 1024; // 10 MB
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('Invalid file type');
}
if (file.size > MAX_SIZE) {
throw new Error('File too large');
}
Secure Key Generation
Use unique, collision-resistant keys:
import { randomUUID } from 'crypto';
const key = `uploads/${userId}/${randomUUID()}-${file.name}`;
Presigned URL Expiration
Keep presigned URL lifetimes short:
const uploadUrl = await platform.env.STORAGE.generateUploadUrl(
key,
contentType,
{
sizeLimit: 10 * 1024 * 1024, // enforce size limit
expires_in: 300 // 5 minutes
}
);
Environment Details
| Setting | Value |
|---|---|
| Max file size (Free) | 10 MB |
| Max file size (Pro) | 50 MB |
| Max file size (Enterprise) | 500 MB |
The platform automatically enforces upload rate limits per tenant. Default: 60 uploads per minute.
Next Steps
- Platform Services Overview — how all three services fit together
- KV Store API Reference — for key-value storage
- Database API Reference — for document queries