Buckets
Create and configure Supabase Storage buckets on SupaHost. Public versus private buckets, size and file type limits, and how to create buckets from Studio, SQL or supabase-js.
A bucket is a top-level container for files. Each bucket has a name, a public or private setting, and optional rules about which files it accepts. Inside a bucket you organise files with slash-separated paths such as team-7/2026/invoice-1042.pdf, which Storage treats as folders.
Most apps need only a handful of buckets, split by who can read the files: one public bucket for things like product images, and one or more private buckets for user documents. SupaHost sets no limit on the number of buckets in a project.
Public and private buckets
| Public bucket | Private bucket | |
|---|---|---|
| Reading a file by URL | Anyone with the URL, no API key needed | Needs a signed URL or an authorised request |
| Uploading, updating, deleting | Needs an RLS policy | Needs an RLS policy |
| Listing files | Needs an RLS policy | Needs an RLS policy |
| Typical use | Recipe photos, avatars, logos | Invoices, contracts, private attachments |
"Public" only changes how files are read. Writes and listings always go through RLS on storage.objects, so a public bucket with no policies is read-only for everyone except your server code using the service_role key. See Storage access control.
Public files are served at:
https://app-one.acme.supahost.space/storage/v1/object/public/<bucket>/<path>
Create a bucket in Studio
- Go to Dashboard → your project → Database tab → Open Studio (owner only).
- In Studio, open Storage and create a new bucket.
- Give it a name, choose whether it's public, and optionally set an allowed file size and MIME types.
Create a bucket with SQL
Buckets are rows in storage.buckets, so you can create them in a migration. This keeps bucket setup in version control next to your policies.
-- A public bucket for recipe photos: images only, 5 MB each
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values (
'recipe-photos',
'recipe-photos',
true,
5242880, -- bytes
array['image/jpeg', 'image/png', 'image/webp']
)
on conflict (id) do nothing;
-- A private bucket for invoice PDFs
insert into storage.buckets (id, name, public, allowed_mime_types)
values ('invoices', 'invoices', false, array['application/pdf'])
on conflict (id) do nothing;
Run it in Studio's SQL editor or apply it as a migration (see SQL migrations).
Create a bucket with supabase-js
Creating buckets from code needs the service_role key (or an RLS policy on storage.buckets, which most apps don't need). Do this from a server or a setup script, never from browser code.
import { createClient } from '@supabase/supabase-js'
const admin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{ auth: { persistSession: false } }
)
const { error } = await admin.storage.createBucket('invoices', {
public: false,
fileSizeLimit: '10MB',
allowedMimeTypes: ['application/pdf'],
})
if (error && !error.message.includes('already exists')) throw error
Bucket options
public: whether files can be read by URL without authorisation.file_size_limit(fileSizeLimitin supabase-js): the largest file the bucket accepts. Storage's per-bucket limit can be lower than the instance limit, never higher. On SupaHost every upload is capped at 50 MB regardless of the bucket setting.allowed_mime_types(allowedMimeTypes): the content types the bucket accepts. Wildcards likeimage/*are allowed. Leave it empty to accept any type.
The MIME type check uses the contentType sent with the upload, so always set it when uploading from code. See Uploading files.
Change or remove a bucket
// Make a bucket private and tighten its limits
await admin.storage.updateBucket('recipe-photos', {
public: false,
fileSizeLimit: '2MB',
allowedMimeTypes: ['image/webp'],
})
// Delete every object in a bucket, then the bucket itself
await admin.storage.emptyBucket('old-exports')
await admin.storage.deleteBucket('old-exports')
A bucket must be empty before it can be deleted. Changing a bucket from public to private takes effect for new requests straight away, but anyone who already downloaded a file keeps their copy, and browsers may serve cached copies until the cache-control lifetime set at upload runs out.
Naming and layout tips
- Keep bucket names short, lowercase and URL-friendly (
recipe-photos,invoices). They appear in every file URL. - Put the owner first in the path:
<user id>/<file>or<team id>/<file>. RLS policies can then check the first folder withstorage.foldername(name). - Add a unique part to file names (
crypto.randomUUID()) so uploads don't overwrite each other and URLs can be cached for a long time. - Split buckets by access rules, not by file type. Two buckets with the same rules usually belong in one.
Buckets in branches, imports and exports
- Branches: bucket rows are copied to every branch. With "Copy data, users and files" the files come along too; a schema-only branch starts with empty buckets. See Branching.
- Import from Supabase Cloud: buckets and files are copied for you. See Importing from Supabase Cloud.
- Exports: the Backups tab's "Package files" export includes
buckets.jsonwith each bucket's settings. See Exporting your data.