Skip to main content

Bucket Management

A Bucket is the basic container for organizing files in Postgres-Native Cloud Storage. Each Bucket is a row in storage.buckets; object metadata is written to storage.objects and linked back through bucket_id.

If you are familiar with Supabase Storage, think of this as a File Bucket for general-purpose files: images, videos, documents, archives, user uploads, and static assets.

When to split buckets

Split buckets by access model, upload restrictions, and business lifecycle, not just by folder.

ScenarioRecommended bucketTypical pathRecommended access model
User avatarsavatars<uid>/avatar.pngOwner writes; read visibility depends on business needs
Public static assetspublic-assetslogos/cloudbase.pngPublic read, server-side write
Private user filesprivate-files<uid>/<filename>Owner read/write
Team filesteam-files<team_id>/<filename>Join business tables for team membership
Article / order attachmentsarticle-assetsarticles/<article_id>/<filename>Follow the business object's permissions

If two file groups have different permissions, size limits, MIME restrictions, or cleanup policies, they usually deserve separate buckets.

Create a bucket

You can create buckets through JS SDK, HTTP API, SQL, or the management SDK. The examples below use JS SDK and SQL.

JS SDK

import cloudbase from '@cloudbase/js-sdk';

const app = cloudbase.init({ env: '<env-id>' });

const { data, error } = await app.storage.createBucket('avatars', {
public: false,
fileSizeLimit: 5 * 1024 * 1024,
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
});

if (error) {
throw error;
}

console.log(data.id);

SQL

INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'avatars',
'avatars',
false,
5 * 1024 * 1024,
ARRAY['image/png', 'image/jpeg', 'image/webp']
);

For more options, see Quick experience: create a bucket and Create Bucket HTTP API.

Fields

FieldTypeDescription
idtextUnique bucket identifier, used as bucketId in object operations
nametextBucket name, up to 100 characters
publicbooleanPublic flag, default false
file_size_limitbigintPer-file size limit in bytes
allowed_mime_typestext[]Allowed MIME-type whitelist
owner_idtextCreator user ID, written by the server
created_attimestamptzCreation time
updated_attimestamptzUpdate time

Public / Private access model

public is a metadata flag on the bucket. It does not automatically bypass RLS. Whether objects are actually publicly readable is still decided by the SELECT policy on storage.objects.

Bucket shapeRecommended configAccess methodUse cases
Public assetspublic = true + public-read RLSgetPublicUrl() / signed URL / downloadLogos, public images, static assets
Private assetspublic = false + owner RLSdownload() / createSignedUrl()Resumes, order attachments, private files
Team assetspublic = false + RLS joining business tablesdownload() / createSignedUrl()Team drives, project attachments
Official assetspublic = true + public read, service_role write onlygetPublicUrl()Official icons, campaign banners

To make buckets with public = true anonymously readable, add an explicit policy:

CREATE POLICY objects_public_read ON storage.objects
FOR SELECT TO anon, authenticated
USING (
EXISTS (
SELECT 1 FROM storage.buckets b
WHERE b.id = storage.objects.bucket_id
AND b.public
)
);

In production, public read does not mean public write. Control SELECT, INSERT, UPDATE, and DELETE separately.

Upload restrictions

Buckets can define two common upload restrictions:

RestrictionFieldDescription
Per-file sizefile_size_limitUploads larger than the limit are rejected
File typeallowed_mime_typesOnly MIME types in the whitelist are accepted

Example: limit avatar uploads to common image formats within 5 MB:

UPDATE storage.buckets
SET
file_size_limit = 5 * 1024 * 1024,
allowed_mime_types = ARRAY['image/png', 'image/jpeg', 'image/webp']
WHERE id = 'avatars';

For role- or business-dependent limits, combine RLS with metadata. See FAQ: limiting file size / MIME type in RLS.

Update a bucket

const { data, error } = await app.storage.updateBucket('avatars', {
public: false,
fileSizeLimit: 10 * 1024 * 1024,
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
});

if (error) {
throw error;
}

console.log(data.updatedAt);

Or update it directly with SQL:

UPDATE storage.buckets
SET file_size_limit = 10 * 1024 * 1024
WHERE id = 'avatars';

Delete a bucket

Before deleting a bucket, make sure its objects have been cleaned up and no business table still references its paths.

const { error } = await app.storage.deleteBucket('avatars');

if (error) {
throw error;
}

Delete objects through SDK / Storage API. Do not directly DELETE FROM storage.objects, otherwise file bytes may be orphaned. See FAQ.

AI-friendly design tips

To help AI assistants generate correct buckets, paths, and RLS policies, keep the following contract explicit:

ItemExample
Bucket IDavatars, private-files, team-files
Path template<uid>/avatar.png, <team_id>/<filename>
Permission modelowner_id = auth.uid(), JOIN team_members
Upload limitsfile_size_limit = 5242880, allowed_mime_types = ['image/png']
Access methodPublic assets use getPublicUrl(); private assets use createSignedUrl()

Recommended contract format:

Bucket: avatars
Purpose: user avatars
Path: <uid>/avatar.png
Read: anyone can read
Write: only the owner can upload or overwrite their avatar
Limits: 5 MB; image/png, image/jpeg, image/webp