Error de CORS: qué significa y cómo solucionarlo
Qué quiere decir de verdad un error de CORS, cómo leerlo en la consola, sus causas típicas en apps hechas con IA y el arreglo en funciones de Supabase y APIs.
Read in English →
Un error de CORS significa que el navegador no dejó que tu frontend leyera la respuesta de otro origen, porque ese servidor no envió los encabezados que dicen que tu sitio tiene permiso. El arreglo casi siempre va en el servidor al que llamas (o en una pequeña función de backend que haga la llamada por ti), no en el código del frontend. Por eso la misma petición funciona en Postman o curl: CORS solo lo aplican los navegadores.
En esta guía te explicamos qué es un origen, cómo leer el mensaje exacto en la consola, las causas más comunes en apps hechas con Lovable, Bolt, v0 o Cursor y cómo arreglar y comprobar cada una.
¿Qué cuenta como un origen distinto?
Un origen es la combinación de esquema, host y puerto. Si cambia cualquiera de los tres, el navegador lo trata como otro sitio.
| Tu página | La petición va a | ¿Mismo origen? |
|---|---|---|
https://tuapp.com | https://tuapp.com/api/pedidos | Sí |
https://tuapp.com | https://www.tuapp.com/api | No, otro host |
http://localhost:5173 | http://localhost:3000 | No, otro puerto |
https://tuapp.com | https://abc.supabase.co/functions/v1/enviar | No, otro host |
Las llamadas de tu app a una Edge Function de Supabase, a una API de terceros o a tu propio backend en otro dominio son todas de origen cruzado, así que todas necesitan encabezados CORS.
¿Cómo leo el mensaje de error?
Abre las herramientas de desarrollo del navegador, pestaña Console. En Chrome el mensaje suele verse así (aparece en inglés aunque tengas el navegador en español):
Access to fetch at 'https://abc.supabase.co/functions/v1/enviar' from origin
'https://tuapp.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Firefox lo muestra como "Cross-Origin Request Blocked" seguido de un motivo, como CORS header 'Access-Control-Allow-Origin' missing. MDN mantiene una lista de esos motivos con la explicación de cada uno.
Después pasa a la pestaña Network y busca la petición que falló. Dos datos te cuentan casi todo:
- ¿Hay una petición
OPTIONSantes? Es la verificación previa (preflight). Si falló, el problema está en esa respuesta. - ¿Qué código de estado recibió la petición real? Un 500 o un 404 con error de CORS suele significar que el servidor se cayó o que la URL está mal, y que la página de error simplemente no traía encabezados CORS.
¿Qué es la petición de verificación previa?
En muchas peticiones el navegador primero manda un OPTIONS para preguntarle al servidor si la petición real está permitida. Lo hace cuando el método no es GET, HEAD o POST, cuando envías encabezados como Authorization, o cuando el Content-Type no es de formulario o texto plano. Un cuerpo JSON con Content-Type: application/json dispara la verificación, así que la mayoría de las llamadas a APIs de una app moderna la tienen.
El servidor tiene que responder ese OPTIONS con un estado de éxito y con encabezados que permitan tu origen, el método y los encabezados. Si el código del servidor espera un cuerpo JSON y se rompe con la verificación vacía, aparece un error de CORS aunque la petición real hubiera funcionado.
¿Qué suele causar errores de CORS en apps hechas con IA?
- Una Edge Function que no atiende
OPTIONS. Muy común en funciones de Supabase generadas por IA. La función corre su lógica principal con la verificación previa, falla y no devuelve encabezados CORS. - Encabezados solo cuando todo sale bien. La función agrega los encabezados en la respuesta exitosa, pero un error (variable faltante, datos inválidos) devuelve una respuesta sin ellos. La consola muestra CORS y esconde el problema real.
- Llamar a una API de terceros directo desde el navegador. Muchas APIs no permiten llamadas desde navegadores a propósito, y además esas llamadas suelen necesitar una clave secreta que no puede estar en el frontend.
- La lista de orígenes permitidos quedó vieja. El servidor permite
https://tuapp.compero ahora estás enwww.tuapp.com, en un dominio nuevo o en una URL de preview. - Credenciales con comodín. Si envías cookies (
credentials: 'include'), el servidor no puede responderAccess-Control-Allow-Origin: *; tiene que nombrar tu origen exacto y enviar tambiénAccess-Control-Allow-Credentials: true. - Una redirección en el camino. La URL redirige de
httpahttpso agrega una barra final, y esa respuesta de redirección no trae encabezados CORS.
¿Cómo lo arreglo?
En una Edge Function de Supabase
Atiende primero la verificación previa y agrega los mismos encabezados a todas las respuestas, también a las de error. La guía de CORS de Supabase recomienda importar los encabezados desde la librería cliente (versión 2.95.0 o posterior):
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
export default {
fetch: async (req: Request) => {
if (req.method === 'OPTIONS') {
return Response.json({ ok: true }, { headers: corsHeaders })
}
try {
const body = await req.json()
// ... tu lógica
return Response.json({ ok: true }, { headers: corsHeaders })
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return Response.json({ error: message }, { status: 400, headers: corsHeaders })
}
},
}
Esos encabezados por defecto permiten cualquier origen. En funciones que actúan en nombre de un usuario con sesión suele estar bien, porque la función igual revisa el token del usuario. Si quieres restringirlo, cambia el origen permitido por tu dominio.
En tu propia API
Permite solo los orígenes que realmente usas, responde a OPTIONS y declara los métodos y encabezados que envía tu frontend. Una respuesta típica:
Access-Control-Allow-Origin: https://tuapp.com
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: authorization, content-type
Vary: Origin
En una API de terceros
No la llames desde el navegador. Pon la llamada en una función del servidor (una Edge Function, una función de Vercel o Netlify) que guarde la clave y le devuelva al frontend solo lo necesario. En la guía sobre una clave de API expuesta en el frontend explicamos por qué importa. Otra opción es un rewrite en tu hosting para que el navegador vea la API como si fuera del mismo origen. En Vercel se configura en vercel.json:
{ "rewrites": [{ "source": "/api/:path*", "destination": "https://api.example.com/:path*" }] }
Usa el rewrite solo con APIs que no necesitan secreto, porque la petición la sigue enviando el navegador.
¿Qué no conviene hacer?
- No uses
mode: 'no-cors'. La petición sale, pero la respuesta queda ilegible y tu código no recibe nada útil. - No instales una extensión que desactive CORS. Solo esconde el problema en tu computadora.
- No permitas cualquier origen con credenciales. Los navegadores rechazan esa combinación de todos modos.
¿Cómo confirmo que quedó resuelto?
En la pestaña Network, la petición OPTIONS debería devolver 200 o 204 con un Access-Control-Allow-Origin igual al origen de tu página, y la petición real debería mostrar el mismo encabezado. Prueba en tu dominio de producción y en una URL de preview, con sesión y sin sesión. Después provoca un error a propósito (datos inválidos) y revisa que ves el mensaje real, no un error de CORS.
Respuestas rápidas
¿Por qué funciona en Postman y no en el navegador? CORS es una regla del navegador. Postman y curl no la aplican, así que solo prueban que el servidor responde, no que el navegador pueda leer la respuesta.
¿Puedo arreglar CORS en mi código de React? No. El navegador revisa los encabezados de la respuesta del servidor, así que el cambio va en el servidor.
¿Por qué se rompió CORS al conectar mi dominio? El servidor o la función permite un origen concreto, y tu dominio nuevo o el www no está en la lista.
¿Un error de CORS es un problema de seguridad? Es el navegador protegiendo a tus usuarios. El riesgo está en el atajo: permitir cualquier origen con credenciales o pasar una clave secreta al navegador para esquivar el error.
¿Sigues viendo errores de CORS?
Si la consola sigue mostrando CORS y no logras ver qué está diciendo de verdad el servidor, nuestro trabajo de integración de sistemas y APIs y de mantenimiento de software cubre justo esto. Mira también la guía de errores en Edge Functions de Lovable o pasa tu app por el chequeo gratuito.


