Storage RLS

Storage objects participate in the same row-level security engine as the rest of the graph. Policies attach to the storage.Object label (or the storage.objects collection) using the familiar CREATE POLICY syntax.

Standard claims

Every storage RLS predicate has these session variables in scope:

VariableSource
auth.uid()JWT sub claim - current user ID
auth.role()JWT role claim
auth.jwt()Full JWT payload as a map
rowThe :storage.Object being checked

Examples

-- Only the owner can read their objects.
CREATE POLICY avatars_owner_select
  ON LABEL `storage.Object`
  FOR SELECT
  USING (row.owner = auth.uid());

-- Only the owner can upload to their folder.
CREATE POLICY avatars_owner_insert
  ON LABEL `storage.Object`
  FOR INSERT
  WITH CHECK (
    row.owner = auth.uid()
    AND row.path STARTS WITH (auth.uid() || '/')
  );

-- Anyone authenticated can read; only admins can write.
CREATE POLICY storage_admin_write
  ON LABEL `storage.Object`
  FOR INSERT, UPDATE, DELETE
  WITH CHECK ('admin' IN auth.role());

Bypass for service_role

Edge functions and server-side code typically authenticate with the service_role claim, which bypasses all storage RLS (just like Postgres / Supabase). End-user requests always go through the policy gauntlet.

Public bucket vs RLS

The public-download endpoint (/storage/v1/object/public/...) is a deliberate side-channel: it bypasses RLS because the bucket's public: true flag is the authorization. Authenticated downloads through /storage/v1/object/{bucket}/{path} always consult RLS.