# 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.

Source: https://supahost.app/docs/storage/buckets · Updated 2026-11-11

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](/docs/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

1. Go to *Dashboard → your project → **Database** tab → Open Studio* (owner only).
2. In Studio, open **Storage** and create a new bucket.
3. 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.

```sql
-- 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](/docs/database/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.

```ts
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`** (`fileSizeLimit` in 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 like `image/*` 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](/docs/storage/uploads).

## Change or remove a bucket

```ts
// 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 with `storage.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](/docs/platform/branching).
- **Import from Supabase Cloud:** buckets and files are copied for you. See [Importing from Supabase Cloud](/docs/platform/import-from-supabase).
- **Exports:** the Backups tab's "Package files" export includes `buckets.json` with each bucket's settings. See [Exporting your data](/docs/platform/exports).

## Related

- [Storage access control](/docs/storage/access-control)
- [Why can't I upload to or list my public bucket?](/kb/public-bucket-upload)
- [supabase-js `createBucket` reference](https://supabase.com/docs/reference/javascript/storage-createbucket)
