IA Engineering

Supabase Storage : stocker, sécuriser et transformer vos fichiers

Buckets publics ou privés, policies RLS sur storage.objects, envoi de fichiers, transformations d'images, URL signées et suppression. Code corrigé et vérifié sur la documentation Supabase au 30/09/2026.

Une main insère une clé dorée dans le cadenas d'un tiroir transparent d'une armoire contenant des icônes numériques et d'autres cadenas.

L’essentiel en 30 secondes

  • Les droits d'accès de Supabase Storage sont des policies RLS sur la table storage.objects : sans policy, aucun envoi n'est autorisé.
  • Rangez les fichiers de chaque utilisateur dans un dossier à son identifiant et comparez-le avec storage.foldername(name) dans les policies.
  • Les transformations d'images sont réservées au plan Pro et au-dessus ; le WebP est servi automatiquement et format n'accepte que 'origin'.
  • Supprimez les fichiers par l'API Storage, jamais par SQL, sinon ils restent orphelins dans le bucket.

Supabase Storage stocke vos fichiers (images, PDF, vidéos) dans des buckets et en décrit chaque objet dans la table Postgres storage.objects. Conséquence pratique : les droits d'accès s'écrivent comme des policies RLS, en SQL, exactement comme pour vos tables. Sans policy, aucun envoi de fichier n'est autorisé1. Le service est aussi compatible avec le protocole S3, ce qui permet d'utiliser la plupart des clients S3 existants2.

Pour la place de Storage dans l'architecture Supabase, voir notre guide de la plateforme Supabase.

Bucket public ou privé : lequel choisir ?

—Bucket publicBucket privé (défaut)
LectureToute personne qui a l'URLSoumise aux policies RLS, ou via une URL signée temporaire
Envoi, suppression, déplacementSoumis aux policiesSoumis aux policies
Cache CDNPlus performantMis en cache différemment
ExemplesAvatars, images d'articlesFactures, documents personnels

Les buckets sont privés par défaut3.

Comment créer un bucket ?

Créer un bucket est une opération d'administration : faites-la dans une migration SQL, depuis le tableau de bord, ou côté serveur. Les restrictions de type et de taille se définissent à la création, et une requête qui ne les respecte pas est rejetée4.

-- Dans une migration
insert into storage.buckets (id, name, public)
values ('avatars', 'avatars', true);
// Côté serveur, avec le client JavaScript
const { data, error } = await supabase.storage.createBucket('avatars', {
  public: true,
  allowedMimeTypes: ['image/*'],
  fileSizeLimit: '1MB',
})

La taille maximale par bucket ne peut pas dépasser la limite globale du projet : 50 Mo par fichier sur le plan Free, jusqu'à 500 Go sur Pro et Team5.

Comment écrire les policies d'accès ?

Le modèle le plus courant : chaque utilisateur range ses fichiers dans un dossier qui porte son identifiant. La fonction storage.foldername(name) renvoie les dossiers du chemin ; on compare le premier à l'identifiant de l'utilisateur connecté1. Pour autoriser upsert (écraser un fichier existant), il faut en plus les droits SELECT et UPDATE1.

create policy "avatars : lecture de ses fichiers"
on storage.objects for select to authenticated
using (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.jwt()->>'sub'));

create policy "avatars : envoi dans son dossier"
on storage.objects for insert to authenticated
with check (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.jwt()->>'sub'));

create policy "avatars : remplacement de ses fichiers"
on storage.objects for update to authenticated
using (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.jwt()->>'sub'));

create policy "avatars : suppression de ses fichiers"
on storage.objects for delete to authenticated
using (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.jwt()->>'sub'));

Sur un bucket public, la policy SELECT n'est pas nécessaire pour afficher les fichiers ; elle reste utile pour l'upsert. Pour la logique des policies, voir notre article sur la Row Level Security de Supabase.

Comment envoyer un fichier ?

Le chemin doit commencer par l'identifiant de l'utilisateur pour correspondre aux policies ci-dessus :

async function uploadAvatar(file: File, userId: string) {
  const ext = file.name.split('.').pop()
  const path = `${userId}/avatar.${ext}` // premier dossier = id de l'utilisateur

  const { error } = await supabase.storage
    .from('avatars')
    .upload(path, file, { cacheControl: '3600', upsert: true })
  if (error) throw error

  const { data } = supabase.storage.from('avatars').getPublicUrl(path)
  return data.publicUrl
}

Correction : l'ancienne version envoyait le fichier vers avatars/${userId}.ext dans le bucket avatars. Le premier dossier du chemin était donc avatars et non l'identifiant, et la policy fondée sur storage.foldername(name)[1] refusait l'envoi.

Comment redimensionner les images à la volée ?

Les transformations d'images sont réservées aux plans Pro et supérieurs. Elles s'activent ou se coupent dans Storage > Settings6.

const { data } = supabase.storage.from('photos').getPublicUrl('produits/chaise.jpg', {
  transform: {
    width: 400,
    height: 400,
    resize: 'cover', // 'cover' (défaut) | 'contain' | 'fill'
    quality: 80,     // 20 à 100, 80 par défaut
  },
})
  • Format : Storage renvoie automatiquement du WebP aux navigateurs qui le supportent. L'option format ne sert qu'à désactiver ce comportement (format: 'origin') ; l'ancienne valeur format: 'webp' de cet article n'existe pas6.
  • Limites : largeur et hauteur entre 1 et 2 500 pixels, image source de 25 Mo et 50 mégapixels maximum6.
  • Coût : 100 images d'origine incluses par mois sur Pro et Team, puis 5 $ par tranche de 1 0006.
  • Les URL signées acceptent aussi l'option transform ; les paramètres sont alors figés dans le jeton6.

Comment partager un fichier privé ?

Deux voies pour un bucket privé : télécharger avec le JWT de l'utilisateur (les policies décident), ou générer une URL signée valable un temps limité3.

// URL valable une heure (durée en secondes)
const { data, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('factures/facture-123.pdf', 3600)

// Téléchargement authentifié puis enregistrement côté navigateur
const { data: blob } = await supabase.storage.from('documents').download('factures/facture-123.pdf')
if (blob) {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'facture-123.pdf'
  a.click()
  URL.revokeObjectURL(url)
}

Comment lister, déplacer et supprimer ?

const { data: files } = await supabase.storage
  .from('avatars')
  .list(userId, { limit: 100, sortBy: { column: 'created_at', order: 'desc' } })

await supabase.storage.from('photos').move('ancien-nom.jpg', 'nouveau-nom.jpg')

await supabase.storage.from('photos').remove(['photo1.jpg', 'photo2.jpg'])

Supprimez toujours par l'API Storage (remove, 1 000 objets maximum par appel), jamais par un delete SQL sur storage.objects : le fichier resterait dans le bucket, orphelin. Pour vider un bucket de plus de 200 000 objets, Supabase recommande l'AWS CLI via le protocole S37. Enfin, les sauvegardes de la base ne contiennent pas les fichiers Storage, seulement leurs métadonnées8.

Pour traiter les fichiers côté serveur après l'envoi (redimensionnement, extraction de texte), voir les fonctions serverless Edge Functions, ou notre service de développement sur mesure pour un projet complet.

Sources

  1. Storage Access Control (documentation Supabase)
  2. S3 Compatibility (documentation Supabase)
  3. Storage Buckets (documentation Supabase)
  4. Creating Buckets (documentation Supabase)
  5. Storage Limits (documentation Supabase)
  6. Storage Image Transformations (documentation Supabase)
  7. Delete Objects (documentation Supabase)
  8. Database Backups (documentation Supabase)

Écrit par

CTO & Chief Digital Strategist chez AdSim, Liège

Georges est CTO et Chief Digital Strategist d’AdSim.

  • Campaign Manager Brand Controls Basics
  • Bid Manager Brand Controls Basics
  • AdWords Video Brand Controls Basics

Questions fréquentes

Vos questions sur Supabase Storage

Quelle taille maximale de fichier accepte Supabase Storage ?

50 Mo par fichier sur le plan Free. Sur Pro et Team, la limite globale est réglable jusqu'à 500 Go ; au-delà, il faut contacter Supabase. Chaque bucket peut avoir une limite plus basse.

Peut-on utiliser un client S3 avec Supabase Storage ?

Oui, Storage est compatible avec le protocole S3. Les envois standard, reprenables et S3 sont interopérables : un fichier envoyé en S3 se liste avec l'API REST, et inversement.

Les transformations d'images fonctionnent-elles sur le plan gratuit ?

Non, elles sont réservées aux plans Pro et supérieurs, avec 100 images d'origine incluses par mois puis 5 $ par tranche de 1 000.

Commentaires

Chaque commentaire est relu avant publication, en général sous 24 h ouvrées. Les liens promotionnels ne sont pas publiés.

Aucun commentaire pour l’instant. Une question sur l’article ? Posez-la ci-dessous.

Laisser un commentaire

Jamais publié. Sert à vous prévenir d’une réponse.

Votre commentaire sera publié après relecture. Un lien au plus, pas de message promotionnel.

Point de départ

On applique cette méthode à votre compte ?

L’audit gratuit part de vos données, pas d’un exemple. Vous recevez le diagnostic sous 48 h ouvrées.

« Chez AdSim, c’est un vrai expert du digital qui lit votre demande et vous répond sous 48 h ouvrées. »

Valérie Matrige, CEO & co-fondatrice

Réponse sous 48 h ouvrées · Diagnostic 100 % gratuit · Sans engagement · Zéro revente de vos données