Fundamentos de seguridad
Supabase "Bucket not found": las tres cosas que significa
Supabase "Bucket not found" es una de tres cosas: un enlace público a un bucket privado, un nombre que no coincide exactamente o el proyecto equivocado.

En resumen
- Supabase "Bucket not found" significa que Storage buscó un bucket con ese nombre exacto, en el proyecto al que llegó tu petición, y no encontró ninguno que estuviera dispuesto a servir a esa petición. El bucket puede seguir ahí, en tu panel.
- Un enlace creado con getPublicUrl para un bucket privado da este error al abrirlo, porque Storage sirve esos enlaces solo desde buckets públicos.
- En una subida significa que el nombre no existe en ese proyecto. Revisa la ortografía y las mayúsculas, y después a qué proyecto apunta tu app.
- Hacer público el bucket acaba con el mensaje y deja que cualquiera con la dirección de un archivo lo abra. Para archivos que pertenecen a una sola persona, usa un enlace firmado.
Tu app muestra una imagen rota donde debería haber una foto. O falla una subida, y el mensaje que vuelve tiene tres palabras: "Bucket not found". Entonces abres Supabase, vas a Storage y el bucket está ahí mismo, en la lista.
Abre la dirección de la imagen en una pestaña aparte y Storage responde esto:
{
"statusCode": "404",
"code": "NoSuchBucket",
"error": "Bucket not found",
"message": "Bucket not found"
}
Ese es el cuerpo que construye hoy el código fuente de Supabase Storage, leído el 17 de octubre de 2026. Supabase "Bucket not found" cubre tres situaciones distintas con ese mismo cuerpo, y en la primera el bucket está exactamente donde lo dejaste.
Y aquí es donde guía tras guía se equivoca: la primera solución que vas a encontrar es hacer público el bucket, y cuando funciona, funciona porque cada archivo de ese bucket pasa a ser público también. Para dos de las tres causas no cambia absolutamente nada. Para la que queda, cambia una imagen rota por un bucket que puede leer cualquiera que tenga la dirección de un archivo.
¿Qué significa Supabase "Bucket not found"?
Storage buscó un bucket con exactamente el nombre de tu petición, dentro del proyecto al que llegó tu petición, y no encontró ninguno que estuviera dispuesto a servir a esa petición.
Esa última parte está ahí porque un bucket tiene más de una puerta, y cada puerta hace la pregunta a su manera. El código fuente de Storage muestra tres:
- Una dirección pública de archivo, la que genera
getPublicUrl. Esta puerta solo cuenta los buckets marcados como públicos, así que un bucket privado aparece aquí como "no encontrado" aunque exista. - Una subida. Esta puerta cuenta todos los buckets del proyecto, privados incluidos. "No encontrado" significa aquí que ese nombre de verdad no existe en ese proyecto.
- Una petición de la lista de archivos de un bucket. Esta puerta nunca dice "Bucket not found". Un bucket que no existe responde con una lista vacía, la misma respuesta que da un bucket vacío, y eso lo vemos desde fuera cada vez que pregunta nuestro escáner.
Así que lo primero es averiguar por qué puerta entró la petición que falló.
Los dos números de ese mensaje no coinciden, y eso confunde el manejo de
errores. El cuerpo dice "statusCode": "404", mientras que la petición en sí
falla con el estado HTTP 400. Storage envía este error con un 400 y guarda su
propio 404 dentro del cuerpo. La librería supabase-js pasa los dos a tu código:
error.status es 400 y error.statusCode es "404". El código que tu builder
escribió para atrapar un 404 dejará pasar este.
Causa 1: un enlace público a un bucket privado
Esta es la causa de las imágenes rotas. La dirección se construyó para un bucket público, y el bucket es privado.
getPublicUrl no le pregunta nada a Supabase. Junta la dirección de tu
proyecto, el nombre del bucket y el nombre del archivo en un enlace, dentro de tu
app, y te lo devuelve. La nota que hay encima de esa función, en la propia
librería cliente de Supabase, lo dice en dos frases:
Esta función no comprueba si el bucket es público. Si se crea una URL pública para un bucket que no es público, no podrás descargar el archivo.
Más tarde, el navegador de un visitante pide ese enlace, y Storage busca el bucket solo entre los buckets públicos. Un bucket privado no pasa esa búsqueda, igual que uno que no existe, y la respuesta es "Bucket not found".
Puedes reconocer esta causa por el propio enlace. Una dirección pública de
archivo contiene /storage/v1/object/public/ seguido del nombre del bucket. Si
ese bucket no lleva ninguna marca de público en Storage → Buckets, el enlace
no iba a abrirse nunca, se escriba como se escriba el nombre.
Los buckets nuevos empiezan siendo privados, así que el ajuste se está comportando como estaba previsto. Lo que pasó es que el código, tuyo o de tu builder, se escribió para un bucket público y se apuntó a uno privado.
Causa 2: un nombre que no es exactamente el del bucket
Storage compara el nombre del bucket carácter por carácter, así que Avatars y
avatars son dos buckets distintos, igual que avatar y avatars.
En el código fuente de Storage la búsqueda es una igualdad simple sobre el id del bucket. Las mayúsculas cuentan, una letra que falta cuenta, y un guion donde el bucket tiene un guion bajo cuenta. Cada uno de esos casos vuelve como las mismas tres palabras.
Una app hecha con una herramienta de IA puede llegar aquí sin que nadie haya escrito nada mal. El código que sube o muestra un archivo se escribe en un paso, y el bucket que nombra se supone que se crea en otro. Si ese segundo paso nunca se ejecutó, o se ejecutó con un nombre ligeramente distinto, el código está pidiendo un bucket que nadie creó.
Comprobarlo requiere dos listas. Abre Storage → Buckets y apunta el nombre de cada bucket tal como aparece. Después dile a tu builder, con palabras sencillas: haz una lista de cada nombre de bucket de Storage que usa esta app, y muéstrame dónde se usa cada uno. Cualquier nombre de su lista que falte en la tuya es el que falla.
Causa 3: el bucket está en otro proyecto
Tu app habla con un único proyecto de Supabase, el de su dirección de Supabase, y un bucket creado en cualquier otro proyecto simplemente no existe para tu app.
Donde esta causa se ve con más claridad es en una subida. En una subida, Storage busca el bucket en todo el proyecto sin aplicar tus reglas de acceso, así que "Bucket not found" en una subida nunca es un problema de permisos. Cuando el bucket existe y tus reglas rechazan el archivo, Storage responde con otro mensaje, que habla de row-level security. Así que "Bucket not found" en una subida significa que ese nombre no existe en el proyecto en el que escribe tu app.
Hay formas muy normales de acabar con dos proyectos: un proyecto de pruebas junto al de producción, una copia de la app conectada a un proyecto nuevo, un proyecto creado a mano para probar algo. El bucket se crea en el que estuviera abierto en el panel en ese momento.
La comprobación consiste en comparar dos cadenas cortas. La dirección de
Supabase de tu app tiene la forma https://<ref>.supabase.co, y la parte antes
de .supabase.co es la referencia del proyecto. La dirección de tu panel lleva
esa misma referencia después de /project/. Si las dos no coinciden, estás
mirando un proyecto mientras tu app habla con otro.
¿Deberías hacer público el bucket para arreglarlo?
Solo cuando cada archivo del bucket esté pensado para cualquiera que tenga su dirección, y solo para la primera causa. Con un nombre equivocado o un proyecto equivocado, el interruptor no cambia nada.
Un bucket público hace una sola cosa, y la documentación de Supabase la describe en una línea: cualquiera que tenga la dirección de un archivo puede abrirlo. No interviene ningún inicio de sesión. Los propios ejemplos de Supabase son fotos de perfil, contenido multimedia público y contenido de entradas de blog, y los logos o las fotos de producto entran en el mismo grupo.
Es el ajuste equivocado para facturas, escaneos de documentos de identidad, exportaciones o cualquier cosa que pertenezca a una sola persona. El interruptor se aplica a todo el bucket y a cada archivo que llegue después, incluidos los de una función que todavía no has construido.
El mismo atajo tiene un gemelo en el lado de las reglas, y es el que nosotros
medimos. Una regla sobre storage.objects que solo nombra el bucket hace que
las descargas funcionen para todo el mundo, y además entrega la lista de nombres
del bucket. Hemos escaneado 8.435 apps en producción que nombran un proyecto de
Supabase. La comprobación de buckets obtuvo respuesta de 4.703 de ellas, y 792
respondieron a nuestra petición de listado con los nombres de lo que había
dentro, sin nadie con la sesión iniciada. Eso es más o menos una de cada seis
de las apps a las que pudimos preguntar. Cuando un bucket es público y su lista
también está abierta, cada nombre de esa lista es un enlace que funciona.
Cómo mostrar un archivo privado sin hacerlo público
Dale a la página un enlace firmado: una dirección que funciona durante los segundos que tú elijas y después deja de funcionar.
La documentación de Supabase nombra dos formas de sacar un archivo de un bucket
privado. Una es una descarga que lleva la sesión de la persona que ha iniciado
sesión. La otra es createSignedUrl, que genera un enlace "al que se puede
acceder durante un tiempo limitado". Esa segunda ocupa el lugar de
getPublicUrl en cualquier página que muestre una imagen.
Un enlace firmado sigue sometido a tus reglas. Antes de que Storage firme nada,
su código fuente comprueba que la persona que lo pide puede leer ese archivo,
así que un bucket privado necesita una regla select sobre storage.objects
que diga quién puede leer qué.
El error de subida explica cómo se escriben esas reglas,
incluido por qué una regla de lectura que solo nombra el bucket abre también su
lista.
Para pasárselo a tu builder, con palabras sencillas: para el bucket que da el error "Bucket not found", cambia getPublicUrl por createSignedUrl, deja el bucket privado y añade una policy de Storage que deje a cada persona leer solo los archivos que debe ver. Después recarga la página que tenía la imagen rota.
Lo que tus buckets enseñan ahora mismo a los desconocidos
Dos pantallas del panel te dicen lo que configuraste, y una comprobación desde fuera te dice lo que obtiene un desconocido.
Storage → Buckets muestra qué buckets son públicos. Para cada uno, pregúntate
si cada archivo que contiene es algo que le enseñarías a un desconocido.
Storage → Policies muestra las reglas. Una regla select cuya única
condición es el nombre del bucket deja que cualquiera lea ese bucket, lista de
archivos incluida.
Nuestro escaneo gratuito le pide a tu proyecto en producción la lista de archivos de cada bucket usando solo la clave que ya está en el código de tu app, como lo haría un desconocido, y te dice qué buckets respondieron con nombres. Lee nombres y nunca descarga un archivo. Tarda unos 20 segundos y no necesita cuenta: escanea tu app.
Mantener tus archivos donde los pusiste
Después del arreglo de hoy todavía pueden moverse dos cosas: alguien cambia un ajuste mientras persigue otro fallo, y se borra un archivo que alguien necesitaba.
Reeve Monitor sigue comprobando desde fuera:
- la comprobación de seguridad completa, repetida según un calendario, incluida la del listado de buckets
- si tu app está en línea
- un mensaje cuando un resultado cambia, para que un bucket que se abrió durante la noche no espere a que lo mires
- un informe mensual, en lenguaje claro, de lo que ha visto
Reeve Care guarda una copia de lo que hay dentro:
- una copia cifrada de tu base de datos de Supabase según un calendario, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase
- cada copia verificada antes de contar, contando las filas de cada tabla
- también tus archivos subidos, en cuanto conectas una credencial de Storage
- una restauración con un clic, con una copia de seguridad del estado actual hecha antes
- todo lo que hace Monitor
Qué hacer hoy
Qué hacer
- Abre el enlace que falla en una pestaña aparte. Si la dirección contiene
/storage/v1/object/public/y el bucket es privado, es la primera causa. - Copia el nombre del bucket desde Storage → Buckets y compáralo, mayúsculas incluidas, con el nombre que usa tu app.
- Compara la referencia del proyecto en la dirección de Supabase de tu app con la de la dirección de tu panel.
- Deja un bucket privado salvo que cada archivo que contiene esté pensado para cualquiera que tenga su dirección, y sirve los archivos privados con enlaces firmados.
- Lee cada regla
selectsobrestorage.objects. Una que solo nombra el bucket entrega también la lista de archivos.
Empieza por la petición que falló, porque la puerta por la que entró te dice cuál de las tres causas revisar. La lista de seguridad de 10 minutos cubre lo que una app recién lanzada suele dejar abierto además de esto, y lo que expone un bucket público es la siguiente lectura si alguno de los tuyos resultó ser público.
Preguntas frecuentes
¿Por qué Supabase dice que mi bucket no existe si lo estoy viendo?
Porque Storage busca un bucket de forma distinta según la petición. Una dirección pública de archivo, la que genera getPublicUrl, solo se busca entre los buckets públicos, así que un bucket privado que ves en el panel aparece ahí como no encontrado. Si el error vino de una subida, el nombre de tu código no coincide exactamente con ningún bucket del proyecto al que está conectada tu app.
¿El nombre de un bucket de Supabase distingue mayúsculas y minúsculas?
Sí. Storage compara el nombre carácter por carácter, así que Avatars y avatars son dos buckets distintos, igual que avatar y avatars. Copia el nombre desde Storage → Buckets en tu panel y compáralo con el nombre que usa tu app.
¿Debo hacer público mi bucket para arreglar "Bucket not found"?
Solo si cada archivo que contiene está pensado para cualquiera que tenga su dirección, como fotos de perfil o fotos de producto. Público significa que cualquiera con la dirección de un archivo puede abrirlo sin iniciar sesión. Eso solo arregla el error cuando se creó un enlace público para un bucket privado; un nombre equivocado o un proyecto equivocado sigue roto igual. Para archivos que pertenecen a una sola persona, deja el bucket privado y usa un enlace firmado.
¿Cómo muestro un archivo de un bucket privado?
Con un enlace firmado. createSignedUrl genera una dirección que funciona durante los segundos que tú elijas y después deja de funcionar, y ocupa el lugar de getPublicUrl en cualquier página que muestre el archivo. Quien pide el enlace tiene que poder leer el archivo, así que el bucket necesita una regla select sobre storage.objects que diga quién puede leer qué.
¿Por qué el error dice 404 si la petición falló con 400?
Supabase Storage envía este error con el estado HTTP 400 y pone su propio estado, 404, dentro del cuerpo como statusCode. En supabase-js eso llega como error.status 400 y error.statusCode "404", y por eso el código escrito para atrapar un 404 deja pasar este error sin verlo.