Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cors Laravel Package

medz/cors

CORS middleware for Laravel that adds configurable Cross-Origin Resource Sharing headers, handles preflight OPTIONS requests, and supports allowed origins, methods, headers, credentials, and max-age settings. Easy to register and customize for APIs and web apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (Laravel 5.5–7 era; see gotchas for 8+)

composer require medz/cors

The service provider is auto-discovered. On Lumen you must register it manually in bootstrap/app.php:

$app->register(Medz\Cors\Laravel\CorsServiceProvider::class);
$app->routeMiddleware([
    'cors' => Medz\Cors\Laravel\Middlewares\Cors::class,
]);
$app->configure('cors');

2. Publish and read the config first

php artisan vendor:publish --tag=cors

This is the first place to look — the entire behaviour of the package is this one file. Everything else in the middleware is a thin reader of it. Keys follow the barryvdh/laravel-cors camelCase style (paths, allowedOrigins, allowedOriginsPatterns, allowedMethods, allowedHeaders, exposedHeaders, maxAge, supportsCredentials), so verify the exact spelling against the file you actually publish rather than copying snippets from blog posts.

3. Register the route middleware

In app/Http/Kernel.php:

protected $routeMiddleware = [
    // ...
    'cors' => \Medz\Cors\Laravel\Middlewares\Cors::class,
];

4. First use case — a versioned JSON API consumed by a separate SPA

// config/cors.php
return [
    'paths' => ['api/*'],
    'supportsCredentials' => false,
    'allowedOrigins' => ['https://app.example.com', 'http://localhost:3000'],
    'allowedOriginsPatterns' => [],
    'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
    'allowedHeaders' => ['Content-Type', 'Authorization', 'X-Requested-With'],
    'exposedHeaders' => [],
    'maxAge' => 3600,
];
// routes/api.php
Route::middleware('cors')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
});

Hit it once with curl -i -X OPTIONS http://localhost/api/posts -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: GET" — you should get a 204 with Access-Control-Allow-Origin present. That single curl is your fastest smoke test for every config change.

Implementation Patterns

Scope by path, not globally. The paths key already filters, but attaching cors per route group is clearer and keeps the middleware out of web (session/cookie) traffic where a * origin policy quietly makes credentialed requests unsafe:

Route::prefix('api')->middleware('cors')->group(base_path('routes/api.php'));

Credentials-required SPA/backoffice. If you serve cookies or Authorization with withCredentials, you must (a) set supportsCredentials => true and (b) enumerate exact origins — * is rejected by the browser in that combination. Echoing a specific origin also means the response must carry Vary: Origin; if you're behind Cloudflare/nginx microcaching, confirm the header is present or a cached response will hand the wrong origin to the next client.

Wildcard subdomains. Rather than generating a huge origin array, use allowedOriginsPatterns with a regex, e.g. '#^https://([a-z0-9-]+)\.example\.com$#'. Keep the pattern anchored and scheme-qualified or you'll over-permit.

Expose pagination/custom headers. Anything your frontend reads via response.headers.get(...) beyond the CORS-safelisted set needs to be listed in exposedHeaders — typical culprits are X-Total-Count, X-RateLimit-Remaining, Content-Disposition (file downloads), and Location.

Reuse one CORS policy across services. The real differentiator of this package over fruitcake/php-cors is that the same Cors core is wrapped for PSR-15, Slim, Swoft and ThinkPHP. If you have a Laravel API plus a Slim/PSR-15 microservice, you can ship an identical header policy to both instead of maintaining two config dialects. Instantiate the PSR-15 wrapper with the same array you keep in config/cors.php (shared via a small config package or env).

Preflight is handled for you — don't hand-roll it. The middleware intercepts OPTIONS with Access-Control-Request-Method and short-circuits with 204 + the allow-headers/methods/max-age trio. Do not add a catch-all Route::options('{any}') "just in case"; it will mask whether the middleware is actually wired up.

Testing. Laravel's test client runs middleware, so assert the actual headers rather than trusting config:

$response = $this->withHeaders(['Origin' => 'https://app.example.com'])
    ->options('/api/posts', [], ['Access-Control-Request-Method' => 'GET']);

$response->assertStatus(204);
$response->assertHeader('Access-Control-Allow-Origin', 'https://app.example.com');

Add one test per origin class (allowed, disallowed, pattern-matched) — CORS regressions are silent in PHP and only visible in a browser console.

Ordering relative to auth. Place cors before auth/throttle in the middleware stack so preflights never hit authentication. If you rely on $middlewarePriority in Kernel.php, insert the CORS middleware near the top.

Gotchas and Tips

It is archived (last release May 2020). Treat this as a legacy-stack choice. On Laravel 9+ the framework ships fruitcake/php-cors and HandleCors out of the box; adding medz/cors there gives you two middlewares writing Access-Control-* headers, and duplicates/mismatches are a classic phantom bug. Decide deliberately: new Laravel project → use the built-in; existing Laravel 5.5–7 or Lumen app → this package is fine; anything on PHP 8.1+ → budget time for deprecation/type errors and consider forking or migrating.

Config cache. php artisan config:cache snapshots config/cors.php. Every origin edit needs a re-cache (or config:clear in dev). "I changed the config and nothing happened" is almost always this, or a stale preflight in the browser.

Browser preflight cache. maxAge caches the preflight result client-side. During development set maxAge => 0 and use DevTools "Disable cache" / a hard reload; otherwise you'll debug a problem that only exists in a cached OPTIONS response.

allowedOrigins entries are full origins. https://app.example.com — scheme required, no trailing slash, no path, port only when non-default (http://localhost:3000). example.com silently never matches. Case-sensitivity of scheme/host also bites.

nginx can eat your preflight. If OPTIONS returns 405 and the body looks like an nginx error page, the request never reached PHP. Ensure the location block forwards OPTIONS (if ($request_method = OPTIONS) { return 204; } blocks need matching CORS headers added manually, or remove them).

Non-standard ports and redirects. An HTTP→HTTPS redirect strips or rewrites the Origin; a preflight answered by the redirect target has no CORS headers and the browser reports a generic network error. Terminate redirects before the CORS middleware, or exempt OPTIONS.

Lumen specifics. No auto-discovery, no config:cache parity — $app->configure('cors') is mandatory or the middleware falls back to an empty/default policy and the browser sees no headers at all. Also verify the paths filter still works, since Lumen's router differs from Laravel's.

Debugging tip. Wrap curl around the exact browser request: send Origin, Access-Control-Request-Method, and Access-Control-Request-Headers on an OPTIONS, then replay the real verb with the same Origin. Ninety percent of "CORS errors" become obvious when you see whether the header is absent, * when it must be specific, or a single-origin value on a multi-origin app.

Extension points. The header logic sits behind the package's Cors contract, so if you need dynamic behaviour the config can't express (per-tenant origin allowlists driven by a DB table, signed-origin tokens), bind your own implementation over the interface in a service provider rather than post-processing responses in another middleware — you keep the preflight short-circuit and the Vary handling intact. For simpler dynamic cases, resolving and mutating the Cors instance in a service provider boot or a lightweight middleware placed before cors is less invasive.

Escape hatch. If you only ever need this for Laravel, a ~40-line hand-written middleware reading config('cors') and adding Vary: Origin covers most of the surface area and removes an unmaintained dependency. The package earns its keep when you need the shared core across Laravel + PSR-15/Slim services, or when you want its preflight edge cases handled for you on a legacy Laravel version.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components