How to Fix CORS Errors in a Laravel API
By Pawan Singh · Jun 2026
A CORS error is your browser protecting the user, not your server rejecting the request — understanding that distinction is most of the way to fixing it correctly instead of just disabling security checks until the error disappears.
What's Actually Happening
When a frontend on one origin (e.g. https://app.example.com) calls an API on a different origin (https://api.example.com), the browser enforces the Same-Origin Policy by default. CORS (Cross-Origin Resource Sharing) is the mechanism that lets a server explicitly opt certain origins back in via response headers. Critically: the request usually does reach your Laravel app and run successfully — the browser just refuses to hand the response back to the JavaScript that requested it, because the response didn't include permission headers for that origin. This is why a request can show as "successful" in your Laravel logs while the browser console shows a CORS error.
Configuring config/cors.php
Laravel ships CORS handling via the fruitcake/laravel-cors functionality built into the framework (or the standalone package on older versions), configured in config/cors.php:
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['https://app.example.com'],
'allowed_headers' => ['*'],
'supports_credentials' => true,
];
Each field matters more than it looks:
paths— only requests matching these path patterns get CORS headers at all. Forgetting to include a route prefix here (a custom API path outsideapi/*) is a common cause of "I configured CORS and it's still not working."allowed_origins— must match the frontend's exact origin, protocol included.http://localhost:3000andhttps://localhost:3000are different origins as far as CORS is concerned, and so are a bare domain and itswww.subdomain.supports_credentials— must betrueif the frontend sends cookies (session-based auth, including Sanctum's SPA cookie authentication) with requests. When this istrue,allowed_originscan not be a wildcard*— browsers reject the combination of wildcard origin plus credentials as a security measure, so you must list explicit origins.
The Preflight Request
For anything beyond a simple GET (a POST with a JSON body, a custom header, PUT/DELETE/PATCH), the browser first sends an automatic OPTIONS preflight request asking permission before sending the real one. If your Laravel app doesn't respond correctly to OPTIONS with the right CORS headers — often because a middleware or auth guard intercepts it and returns a 401/403 before CORS handling runs — the real request never gets sent at all, and the browser reports it purely as a CORS failure with no other detail.
Sanctum SPA Authentication Specifically
If you're using Sanctum's cookie-based SPA authentication (not bearer tokens), there's a second, separate configuration point beyond cors.php: SANCTUM_STATEFUL_DOMAINS in .env must list the frontend's domain, and the frontend must call /sanctum/csrf-cookie before its first authenticated request. Missing either of these produces symptoms that look identical to a plain CORS misconfiguration, which is why Sanctum-based setups are disproportionately represented in "I fixed CORS but it's still broken" reports.
Debugging Systematically
- Open the browser's Network tab, not just the console error — find the actual failed request and check whether it's the
OPTIONSpreflight or the real request that's failing. - Check the response headers on that request for
Access-Control-Allow-Origin— if it's missing entirely, Laravel's CORS middleware isn't running for that path (checkpathsinconfig/cors.php). - If the header is present but doesn't match your frontend's exact origin, fix
allowed_origins. - If preflight succeeds (200) but the real request still fails, the problem has moved from CORS to something else — usually authentication (missing/expired Sanctum cookie) or an unrelated server error.
Common Issues
- Works in Postman, fails in the browser — expected. Postman doesn't enforce CORS at all; only browsers do. This is not evidence the API itself is broken.
- CORS error only on POST/PUT/DELETE, GET works fine — a failing or blocked preflight
OPTIONSrequest; check middleware ordering. - Wildcard origin with credentials — browsers reject this combination outright; list explicit origins instead of
*oncesupports_credentialsis true. - Works locally, fails in production —
allowed_originsstill pointing at a local dev URL rather than the production frontend domain.
Need a decoupled frontend and Laravel API talking to each other cleanly? Get in touch — I work as a freelance Laravel developer.