Storage
Manage files with Supabase Storage — buckets, policies, the FILE data type, and the built-in file browser
Overview
Supasheet integrates Supabase Storage at three levels:
- The
FILE/AVATARdata types automatically upload to theuploadsbucket and store metadata on your rows. - Three pre-configured buckets (
public,personal,uploads) cover the common access patterns. - A built-in file browser at
/storage/$bucketIdlets users browse, upload, rename, move, and preview files in any bucket they can access.
All buckets and policies are defined in the migrations under supabase/migrations/ — primarily 20251005041214_general_storage.sql and 20251005051303_uploads.sql.
Pre-Configured Buckets
| Bucket | Public URL? | Read | Write |
|---|---|---|---|
public | Yes (anyone with the URL) | Public + authenticated | Authenticated; updates/deletes by owner |
personal | Yes | Owner only (authenticated) | Owner only (authenticated) |
uploads | Yes | Gated by schema.table:select (or owner-only under auth/) | Gated by schema.table:insert/update/delete (or owner-only under auth/) |
public
Use for assets that anyone with the URL should be able to read — product images, blog covers, marketing media.
| Operation | Who |
|---|---|
SELECT | public (anyone, no auth required) |
INSERT | Any authenticated user |
UPDATE | Owner (owner_id = auth.uid()) |
DELETE | Owner |
personal
Per-user private storage — documents, drafts, sensitive uploads.
| Operation | Who |
|---|---|
SELECT | Owner |
INSERT | Owner |
UPDATE | Owner |
DELETE | Owner |
uploads (used by the FILE type)
This is the bucket Supasheet writes to when a user adds a file through a FILE or AVATAR column. Access is governed by your per-table permissions rather than ownership:
- Path layout:
uploads/<schema>/<table>/<column>/<filename> SELECTrequires<schema>.<table>:selectINSERTrequires<schema>.<table>:insertUPDATE/DELETErequire the matching permission
This means anyone who can read a row can read its attached files, anyone who can edit a row can replace them, and so on — without you writing custom storage policies.
There's one exception carved out inside the same bucket: the uploads/auth/<uid>/... subpath, used for account profile pictures. Objects under auth/ skip the schema.table:action permission check entirely — a signed-in user has full SELECT/INSERT/UPDATE/DELETE on auth/<their own uid>/... and nothing outside it. There is no separate account_image bucket; avatars are just another prefix inside uploads.
This auth/ carve-out is the one place in uploads where access is owner-based instead of permission-based — every other path in the bucket follows the <schema>.<table>:<action> rule above.
Using FILE Columns
The FILE data type stores an array of file objects ({ name, type, size, url, last_modified }). Combine it with a column comment to constrain what the UI accepts:
CREATE TABLE store.products (
id UUID PRIMARY KEY DEFAULT extensions.uuid_generate_v4(),
name TEXT NOT NULL,
image FILE,
manuals FILE
);
COMMENT ON COLUMN store.products.image IS '{"accept": "image/*", "maxSize": 5242880, "maxFiles": 1}';
COMMENT ON COLUMN store.products.manuals IS '{"accept": ".pdf", "maxSize": 10485760, "maxFiles": 5}';See Data Types for the full type reference and Metadata → File columns for the column metadata options.
FILE_OBJECT (single file)
If a column should accept exactly one file (e.g. a cover image), use the FILE_OBJECT composite type:
CREATE TABLE desk.tasks (
id UUID PRIMARY KEY DEFAULT extensions.uuid_generate_v4(),
cover FILE_OBJECT
);
COMMENT ON COLUMN desk.tasks.cover IS '{"accept": "image/*"}';AVATAR (single image, for profiles)
AVATAR is FILE_OBJECT with semantic meaning — Supasheet renders it as a circular avatar.
CREATE TABLE blog.authors (
id UUID PRIMARY KEY DEFAULT extensions.uuid_generate_v4(),
name TEXT NOT NULL,
avatar AVATAR
);
COMMENT ON COLUMN blog.authors.avatar IS '{"maxSize": 2097152}';The File Browser
Authenticated users see a Storage entry in the sidebar that lists buckets they can access. Clicking a bucket opens /storage/$bucketId, which provides:
- Folder navigation with breadcrumbs
- Multi-file upload (drag-and-drop or the upload button)
- Rename, move, and delete actions
- Image / PDF / text previews
Each operation goes through the standard Supabase Storage API — RLS policies on storage.objects are the source of truth.
Creating Custom Buckets
If you need stricter or different access patterns, define your own bucket alongside its policies:
INSERT INTO storage.buckets (id, name, public)
VALUES ('invoices', 'invoices', false);
CREATE POLICY "users_read_own_invoices"
ON storage.objects FOR SELECT TO authenticated
USING (
bucket_id = 'invoices'
AND owner_id = auth.uid()
);
CREATE POLICY "users_upload_own_invoices"
ON storage.objects FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'invoices'
AND owner_id = auth.uid()
);The bucket will appear automatically in the storage sidebar for users whose policies grant them SELECT on it.
Storage Limit Cheat-Sheet
Useful constants when setting maxSize:
| Size | Bytes |
|---|---|
| 1 MB | 1048576 |
| 2 MB | 2097152 |
| 5 MB | 5242880 |
| 10 MB | 10485760 |
| 25 MB | 26214400 |
| 50 MB | 52428800 |
| 100 MB | 104857600 |
Supabase Storage also enforces a global file_size_limit per project. Make sure your maxSize is below the project limit, otherwise uploads will fail at the storage layer.
Next Steps
- Data Types —
FILE,FILE_OBJECT,AVATAR,RICH_TEXT - Metadata — Column-level configuration for file fields
- Authorization — How
schema.table:*permissions gate theuploadsbucket