You’ve built your Laravel API, your frontend is ready, and then — boom. The browser throws this at you:
Access to XMLHttpRequest at ‘http://api.yourdomain.com’ from origin
‘http://frontend.yourdomain.com’ has been blocked by CORS policy:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
If you’ve seen that error, you know the frustration. Everything works fine in Postman. Your routes are correct. Your controller logic is solid. But the browser just… refuses. And you’re sitting there thinking, “What did I even do wrong?”
Nothing, actually. This is a CORS issue — and it trips up almost every developer building a Laravel API for the first time. The good news? It’s completely fixable, and this guide will walk you through exactly how to fix CORS issues in Laravel API, step by step.
What Is CORS, Anyway?
CORS stands for Cross-Origin Resource Sharing. Think of it like a security guard at the entrance of a building. Your browser is the guard, and it won’t let your frontend (from one domain) talk to your backend API (on another domain) unless the backend explicitly says, “Yeah, this visitor is allowed in.”
That permission comes in the form of HTTP response headers — specifically Access-Control-Allow-Origin. If that header isn’t present or doesn’t match your frontend’s origin, the browser blocks the request. The server received it fine — the browser is the one saying no.
It’s a browser-enforced security policy. Mobile apps, Postman, and server-to-server calls don’t care about CORS. That’s why things work in Postman but break in your browser.
Why CORS Issues Happen in Laravel
Here are the most common reasons you’ll run into a Laravel CORS error:
- Your frontend and backend run on different origins. Even
localhost:3000vslocalhost:8000counts as different origins. - Missing or misconfigured CORS headers in the API response.
- Preflight OPTIONS requests aren’t handled — the browser sends an OPTIONS request before the real one, and if Laravel doesn’t respond correctly, the whole thing fails.
- Your CORS middleware isn’t registered properly in the Laravel middleware stack.
- You’re allowing the wrong methods or headers — for example, forgetting to allow
Authorizationwhen you’re using token-based auth.
Any one of these can block your frontend dead in its tracks.
How to Fix CORS Issues in Laravel — Step by Step
Step 1: Know Your Laravel Version
If you’re on Laravel 9 or later, great news — CORS support is built in via the fruitcake/laravel-cors package (which is now maintained as part of Laravel core). You don’t need to install anything extra.
For Laravel 7 or 8, you may need to install it manually:
composer require fruitcake/laravel-corsThen publish the config:
php artisan vendor:publish --tag="cors"This creates a config/cors.php file where you’ll do most of your work.
Step 2: Configure config/cors.php
Open up config/cors.php. Here’s what a typical configuration looks like — and what each option actually means:
return [
/*
* Which paths should have CORS applied.
* 'api/*' covers all your API routes.
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
/*
* Allowed HTTP methods.
* Include OPTIONS for preflight requests.
*/
'allowed_methods' => ['*'],
/*
* Which origins are allowed to make requests.
* Use specific domains in production — never just '*' with credentials.
*/
'allowed_origins' => ['http://localhost:3000', 'https://yourfrontend.com'],
/*
* Patterns — useful if you have dynamic subdomains.
*/
'allowed_origins_patterns' => [],
/*
* Headers the frontend is allowed to send.
*/
'allowed_headers' => ['*'],
/*
* Headers the browser is allowed to read from the response.
*/
'exposed_headers' => [],
/*
* How long the browser caches preflight results (in seconds).
*/
'max_age' => 0,
/*
* Required if you're sending cookies or Authorization headers.
*/
'supports_credentials' => false,
];The two things that bite most developers: allowed_origins (not broad enough) and supports_credentials (set to false when they’re using token auth with cookies). More on that in a minute.
Step 3: Register the CORS Middleware
For the CORS headers to actually be sent, the middleware needs to run on every API request.
In Laravel 9+, the HandleCors middleware is already registered globally in app/Http/Kernel.php under $middleware. Double-check it’s there:
protected $middleware = [
\Fruitcake\Cors\HandleCors::class,
// ... other middleware
];If you’re using Laravel 11, the middleware is registered in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->use([
\Illuminate\Http\Middleware\HandleCors::class,
]);
})Make sure it’s listed before any routing middleware. CORS headers need to be set early — including on failed requests.
Step 4: Set Proper Headers for Your Use Case
If your frontend is sending an Authorization: Bearer token, you need to make sure the header is allowed:
'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],And if you’re using Laravel Sanctum with cookie-based auth (SPA authentication), you’ll need:
'supports_credentials' => true,
'allowed_origins' => ['http://localhost:3000'], // Must be exact — no wildcard with credentialsYou also need to set withCredentials: true on your frontend Axios or Fetch call. Just remember: when supports_credentials is true, you cannot use * as your allowed origin. The browser will reject it.
Step 5: Test Your API
After making config changes, always clear your config cache:
php artisan config:clear
php artisan cache:clearThen test using:
- Postman: Add an
Originheader matching your frontend URL. Check ifAccess-Control-Allow-Originappears in the response headers. - Browser DevTools: Open the Network tab, look for your API request, and check the response headers.
- cURL: Quick test directly from terminal:
curl -I -X OPTIONS http://your-api.com/api/user \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: GET"You should see Access-Control-Allow-Origin in the response.
Common Mistakes That Keep CORS Broken
Using * with credentials. If supports_credentials is true, you must specify exact origins. Using '*' alongside credentials is invalid and the browser will block it regardless.
Forgetting OPTIONS method. Preflight requests use HTTP OPTIONS. If your allowed_methods doesn’t include it (or '*'), preflight will fail and every request will appear broken.
CORS middleware in the wrong place. If HandleCors runs after route resolution, requests that return a 404 or 401 won’t include CORS headers — and your frontend will get a cryptic network error instead of the actual HTTP error.
Config cache not cleared. Laravel caches config in production. Change the config, forget to run config:clear, and wonder why nothing changed. Classic.
Debugging Tips That Actually Help
Check the browser console first. The error message usually tells you exactly what’s missing — whether it’s the Access-Control-Allow-Origin header, a blocked method, or a missing header.
Use the Network tab. Filter by your API endpoint. Look at the response headers for the OPTIONS preflight request and the actual request. If CORS headers are missing entirely, the middleware isn’t running. If the origin doesn’t match, check your allowed_origins config.
Check Laravel logs. If requests are failing before they hit your controller (like auth failures), CORS headers might not be added. Check storage/logs/laravel.log for any exceptions.
Temporarily allow all origins. When debugging, you can set 'allowed_origins' => ['*'] to confirm the issue is CORS config and not something else. Just don’t ship that to production.
A Real-World Example
A developer built a Vue.js frontend talking to a Laravel API. Everything worked locally — both apps on localhost. Deployed to staging? Immediate CORS errors.
The problem: in staging, the frontend was at https://appstage.example.com and the API at https://apistage.example.com. The allowed_origins in cors.php still had http://localhost:3000 — never updated for the real domain.
The fix was a two-minute config change and a config:clear. But it took an hour to figure out because the developer kept looking at the code instead of the config.
The lesson: always check your allowed_origins matches the actual production URL, including the protocol (https:// not http://).
Best Practices for Production
- Never use
allowed_origins => ['*']in production if you’re dealing with authenticated users or sensitive data. Always list your actual frontend domains. - Use environment variables for origins so they can differ between staging and production:
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],- Keep
allowed_methodsspecific if you know what your API supports. No need to allowDELETEif you don’t use it. - Set a reasonable
max_age(like86400for 24 hours) so browsers cache preflight results and don’t send OPTIONS requests on every single API call. - Review your CORS config any time you add a new frontend domain or change deployment environments.
CORS errors feel more mysterious than they are. Once you understand what’s happening — the browser is enforcing a permission system, and your Laravel API needs to explicitly grant that permission — the fix becomes straightforward.
To recap: make sure config/cors.php has the right allowed_origins, that HandleCors middleware is registered globally and early, that OPTIONS method is allowed for preflight, and that you clear the config cache after every change.
Go ahead and apply these fixes — most CORS issues in Laravel resolve in under 10 minutes once you know where to look. And the next time you see that Access-Control-Allow-Origin error, you’ll know exactly what to do.
If this helped you, consider sharing it with your team — CORS issues waste a surprising amount of developer time, and the fix really is this simple.


Leave a Reply