Day 3 of 5
⏱ ~60 minutes
Supabase in 5 Days — Day 3

Storage: Files and Images

Create buckets, upload files from the browser, serve images with transformation URLs, and set policies.

Supabase Storage Buckets

Create a bucket
// In SQL Editor:
-- Or use the Storage UI in the dashboard
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);
Upload from the browser
import { supabase } from './supabase';

async function uploadAvatar(file, userId) {
  const fileExt = file.name.split('.').pop();
  const filePath = `${userId}/avatar.${fileExt}`;

  const { error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      upsert: true  // replace if exists
    });

  if (error) throw error;

  // Get the public URL
  const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath);

  return data.publicUrl;
}

// In a React component:
async function handleFileInput(event) {
  const file = event.target.files[0];
  const url = await uploadAvatar(file, user.id);
  setAvatarUrl(url);
}
Storage RLS policies
-- Allow users to upload to their own folder
CREATE POLICY 'Users upload own avatar' ON storage.objects
  FOR INSERT WITH CHECK (
    bucket_id = 'avatars' AND
    auth.uid()::text = (storage.foldername(name))[1]
  );

-- Public read for avatar bucket
CREATE POLICY 'Public read avatars' ON storage.objects
  FOR SELECT USING (bucket_id = 'avatars');
📝 Day 3 Exercise
Build an Avatar Upload
  1. C
  2. r
  3. e
  4. a
  5. t
  6. e
  7. a
  8. n
  9. a
  10. v
  11. a
  12. t
  13. a
  14. r
  15. s
  16. b
  17. u
  18. c
  19. k
  20. e
  21. t
  22. .
  23. B
  24. u
  25. i
  26. l
  27. d
  28. a
  29. f
  30. i
  31. l
  32. e
  33. i
  34. n
  35. p
  36. u
  37. t
  38. t
  39. h
  40. a
  41. t
  42. u
  43. p
  44. l
  45. o
  46. a
  47. d
  48. s
  49. a
  50. n
  51. i
  52. m
  53. a
  54. g
  55. e
  56. t
  57. o
  58. <
  59. c
  60. o
  61. d
  62. e
  63. >
  64. u
  65. s
  66. e
  67. r
  68. I
  69. d
  70. /
  71. a
  72. v
  73. a
  74. t
  75. a
  76. r
  77. .
  78. j
  79. p
  80. g
  81. <
  82. /
  83. c
  84. o
  85. d
  86. e
  87. >
  88. .
  89. D
  90. i
  91. s
  92. p
  93. l
  94. a
  95. y
  96. t
  97. h
  98. e
  99. u
  100. p
  101. l
  102. o
  103. a
  104. d
  105. e
  106. d
  107. i
  108. m
  109. a
  110. g
  111. e
  112. u
  113. s
  114. i
  115. n
  116. g
  117. t
  118. h
  119. e
  120. p
  121. u
  122. b
  123. l
  124. i
  125. c
  126. U
  127. R
  128. L
  129. .
  130. A
  131. d
  132. d
  133. R
  134. L
  135. S
  136. s
  137. o
  138. u
  139. s
  140. e
  141. r
  142. s
  143. c
  144. a
  145. n
  146. o
  147. n
  148. l
  149. y
  150. o
  151. v
  152. e
  153. r
  154. w
  155. r
  156. i
  157. t
  158. e
  159. t
  160. h
  161. e
  162. i
  163. r
  164. o
  165. w
  166. n
  167. a
  168. v
  169. a
  170. t
  171. a
  172. r
  173. .

Day 3 Summary

  • Buckets organize files. Set public=true for publicly accessible files.
  • storage.upload(path, file)storage.getPublicUrl(path) — the basic pattern.
  • Storage RLS uses the same syntax as table RLS — policies on storage.objects.
  • Use upsert: true to replace files without deleting first.
Finished this lesson?