Skip to content

Upload & Download

Pulsabase Storage uses a presigned URL pattern for all file uploads: the SDK requests a short-lived signed URL from the backend, then uploads the file directly to S3/MinIO — keeping your server out of the critical path for large files.

import pb from './pulsabase';
// Upload a file to the 'public/avatars' path
const result = await pb.storage.upload('photo.jpg', file, {
contentType: 'image/jpeg', // MIME type
path: 'public/avatars', // Virtual folder path
});
console.log(result.objectId); // Unique object ID
console.log(result.publicUrl); // CDN URL if the path is public
1. SDK → POST /storage/upload-url
(requests a presigned upload URL for 'public/avatars/photo.jpg')
2. Backend → validates ACL policy for the given path and user JWT
3. Backend → generates presigned PUT URL (valid ~15 minutes) from MinIO
4. SDK → PUT <presigned_url> with file data
(uploads directly to S3/MinIO — bypasses the backend)
5. SDK → POST /storage/confirm
(notifies backend to mark the object as uploaded)

This approach means even very large files (500MB+) don’t consume backend memory or connections.

// Get a presigned download URL for a specific object
const { presigned_url } = await pb.storage.download(objectId);
// Use the URL directly — e.g. redirect or fetch
window.open(presigned_url);
// List all files in a path prefix
const files = await pb.storage.list({ path: 'public/avatars' });
files.forEach((file) => {
console.log(file.name, file.size, file.contentType);
});
await pb.storage.delete(objectId);

After uploading, store the returned objectId in your database table to associate files with records:

const result = await pb.storage.upload('avatar.jpg', file, {
contentType: 'image/jpeg',
path: `users/${currentUser.sub}`, // Per-user folder
});
// Save the objectId to the user's profile
await pb.from(User).where('id', currentUser.sub).update({
avatar_object_id: result.objectId,
});
// Later, download:
const { presigned_url } = await pb.storage.download(user.avatar_object_id);