Installation:
composer require fruitcake/laravel-cors
Publish the config file:
php artisan vendor:publish --provider="Fruitcake\Cors\CorsServiceProvider" --tag="config"
Basic Configuration:
Edit config/cors.php to define allowed origins, methods, and headers. Example:
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
First Use Case:
Add the middleware to your app/Http/Kernel.php:
protected $middleware = [
\Fruitcake\Cors\HandleCors::class,
];
Now, requests to routes under paths will automatically include CORS headers.
Dynamic Origin Handling:
Use allowed_origins_patterns for regex-based origin validation:
'allowed_origins_patterns' => [
'^https://(.*\.)?example\.com$',
'^https://(.*\.)?client\.app$',
],
Route-Specific CORS: Override CORS settings for specific routes via middleware groups:
Route::middleware(['cors:api-only'])->group(function () {
Route::get('/api/secure', function () {
return response()->json(['data' => 'Secure']);
});
});
Define api-only in config/cors.php:
'groups' => [
'api-only' => [
'allowed_origins' => ['https://trusted-client.com'],
'allowed_methods' => ['GET', 'POST'],
],
],
Credentials Support: Enable CORS for credentials (cookies/auth headers) by setting:
'supports_credentials' => true,
Ensure Access-Control-Allow-Origin is explicit (not *).
Preflight Requests:
Handle OPTIONS requests dynamically by extending the middleware:
Cors::addAllowedMethod('PATCH');
Cors::addAllowedHeader('X-Custom-Header');
Conditional CORS: Use middleware closures to conditionally apply CORS:
Cors::addMiddleware(function ($request) {
if ($request->user()?->isAdmin()) {
return ['allowed_origins' => ['*']];
}
return [];
});
Credentials Conflict:
supports_credentials: true requires explicit Access-Control-Allow-Origin (cannot use *).curl -X GET -H "Origin: https://client.com" --include --header "Cookie: session=...".Middleware Order:
HandleCors before SubstituteBindings or VerifyCsrfToken to avoid header overrides.Kernel.php:
protected $middleware = [
\Fruitcake\Cors\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// ...
];
Path Matching:
paths uses Laravel’s router, so api/* matches /api/users but not /api/v1/users. Use:
'paths' => ['api/v1/*', 'api/v2/*'],
~^(?!api/v1).+$~ in paths.Caching Headers:
max_age: 0 disables caching. Set to 86400 (1 day) for production:
'max_age' => 86400,
CSRF Token Conflicts:
sanctum/csrf-cookie and X-CSRF-TOKEN header:
'paths' => ['sanctum/csrf-cookie'],
'allowed_headers' => ['X-CSRF-TOKEN', 'Authorization'],
Check Headers:
Use dd($request->headers) or browser dev tools to verify Access-Control-* headers.
Preflight Fails:
OPTIONS requests are handled (Laravel routes them by default).Access-Control-Request-Method and Access-Control-Request-Headers match your config.Dynamic Config: Override settings at runtime:
Cors::setAllowedOrigins(['https://new-client.com']);
Logging:
Enable debug mode in config/cors.php:
'debug' => env('APP_DEBUG', false),
Logs CORS decisions to storage/logs/laravel.log.
Custom Middleware:
Extend HandleCors to add logic:
namespace App\Http\Middleware;
use Fruitcake\Cors\HandleCors as BaseCors;
class CustomCors extends BaseCors {
public function handle($request, Closure $next) {
$this->cors->setAllowedOrigins([$request->input('origin')]);
return parent::handle($request, $next);
}
}
Event Listeners:
Listen to cors.before and cors.after events in EventServiceProvider:
protected $listen = [
'cors.before' => [
\App\Listeners\LogCorsRequest::class,
],
];
API Resources: Attach CORS headers to API responses dynamically:
return response()->json($resource)
->header('Access-Control-Expose-Headers', 'X-Custom-Header');
How can I help you explore Laravel packages today?