PHPackages                             effectra/cors - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. effectra/cors

ActiveLibrary[HTTP &amp; Networking](/categories/http)

effectra/cors
=============

PHP library for enabling CORS (Cross-Origin Resource Sharing) in HTTP requests/responses. It follows PSR guidelines, and can be easily integrated into PHP projects.

v2.0.0(3w ago)224MITPHP

Since Jan 5Pushed 2y agoCompare

[ Source](https://github.com/effectra/cors)[ Packagist](https://packagist.org/packages/effectra/cors)[ RSS](/packages/effectra-cors/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (2)Dependencies (15)Versions (3)Used By (0)

effectra/cors
=============

[](#effectracors)

A PHP library for handling **Cross-Origin Resource Sharing (CORS)** in HTTP applications. Built on PSR-7, PSR-15, and PSR-17 standards, it provides a flexible middleware and a standalone service class that can be integrated into any PHP project or framework.

Features
--------

[](#features)

- ✅ PSR-15 compliant middleware
- ✅ Wildcard and pattern-based origin matching
- ✅ Preflight (`OPTIONS`) request handling
- ✅ Credential-aware CORS enforcement
- ✅ Path allowlist and blocklist with wildcard support
- ✅ Debug mode with error logging
- ✅ Runtime error collection (`X-CORS-Errors` header)
- ✅ Both `camelCase` and `snake_case` option key support
- ✅ Automatic `Vary` header management

---

Requirements
------------

[](#requirements)

- PHP 8.0+
- `psr/http-message` ^2.0
- `psr/http-server-handler` ^1.0
- `psr/http-server-middleware` ^1.0

---

Installation
------------

[](#installation)

```
composer require effectra/cors
```

---

Quick Start
-----------

[](#quick-start)

```
use Effectra\Cors\CorsService;
use Effectra\Cors\CorsMiddleware;

$corsService = new CorsService([
    'allowedOrigins'     => ['https://example.com'],
    'allowedMethods'     => ['GET', 'POST', 'PUT', 'DELETE'],
    'allowedHeaders'     => ['Content-Type', 'Authorization'],
    'exposedHeaders'     => ['X-Custom-Header'],
    'supportsCredentials'=> false,
    'maxAge'             => 3600,
]);

$middleware = new CorsMiddleware($corsService, [
    'paths'          => ['api/*'],
    'excluded_paths' => ['api/internal/*'],
]);
```

---

Classes
-------

[](#classes)

### `CorsService`

[](#corsservice)

The core CORS engine. Inspects requests, validates origins, and injects the appropriate CORS response headers.

#### Constructor

[](#constructor)

```
new CorsService(array $options = [])
```

Optionally pass a configuration array at construction time. The same array can be applied later via `setOptions()`.

---

#### Configuration Options

[](#configuration-options)

Key (camelCase)Key (snake\_case)TypeDefaultDescription`allowedOrigins``allowed_origins``string[]``[]`Exact origins allowed. Use `['*']` to allow all.`allowedOriginsPatterns``allowed_origins_patterns``string[]``[]`Regex-style wildcard patterns, e.g. `['https://*.example.com']`.`allowedMethods``allowed_methods``string[]``[]`HTTP methods to allow. Use `['*']` to allow all.`allowedHeaders``allowed_headers``string[]``[]`Request headers to allow. Use `['*']` to allow all.`exposedHeaders``exposed_headers``string[]``[]`Response headers the browser may read.`supportsCredentials``supports_credentials``bool``false`Allow cookies/auth. Incompatible with `allowedOrigins: ['*']`.`maxAge``max_age``int|null``0`Preflight cache duration in seconds. `null` omits the header entirely.> **Note:** Both `camelCase` and `snake_case` keys are accepted everywhere.

---

#### Public Methods

[](#public-methods)

##### `setOptions(array $options): void`

[](#setoptionsarray-options-void)

Apply (or re-apply) a configuration array. Validates all values before storing them and throws `InvalidArgumentException` on invalid input.

```
$corsService->setOptions([
    'allowedOrigins' => ['https://app.example.com'],
    'allowedMethods' => ['GET', 'POST'],
]);
```

---

##### `setDebugMode(bool $debug): self`

[](#setdebugmodebool-debug-self)

Enable debug mode. When active, CORS errors are written to the PHP error log and exceptions are re-thrown instead of being swallowed.

```
$corsService->setDebugMode(true);
```

---

##### `isCorsRequest(RequestInterface $request): bool`

[](#iscorsrequestrequestinterface-request-bool)

Returns `true` when the request carries a non-empty `Origin` header — the sign of a cross-origin request.

```
if ($corsService->isCorsRequest($request)) {
    // handle CORS
}
```

---

##### `isPreflightRequest(RequestInterface $request): bool`

[](#ispreflightrequestrequestinterface-request-bool)

Returns `true` when the request is an `OPTIONS` preflight — i.e., the method is `OPTIONS` **and** `Access-Control-Request-Method` is present.

```
if ($corsService->isPreflightRequest($request)) {
    $response = $corsService->handlePreflightRequest($request);
}
```

---

##### `isOriginAllowed(RequestInterface $request): bool`

[](#isoriginallowedrequestinterface-request-bool)

Checks whether the request `Origin` is permitted by the current configuration. Supports exact matches and wildcard patterns. Also validates the origin URL format.

```
$allowed = $corsService->isOriginAllowed($request); // true | false
```

---

##### `handlePreflightRequest(RequestInterface $request): ResponseInterface`

[](#handlepreflightrequestrequestinterface-request-responseinterface)

Builds and returns a complete preflight response (`204 No Content` on success, `403 Forbidden` when the origin is not allowed). Delegates header injection to `addPreflightRequestHeaders()`.

```
$response = $corsService->handlePreflightRequest($request);
```

---

##### `addPreflightRequestHeaders(ResponseInterface $response, RequestInterface $request): ResponseInterface`

[](#addpreflightrequestheadersresponseinterface-response-requestinterface-request-responseinterface)

Injects all relevant preflight headers into an existing response:

- `Access-Control-Allow-Origin`
- `Access-Control-Allow-Credentials`
- `Access-Control-Allow-Methods`
- `Access-Control-Allow-Headers`
- `Access-Control-Max-Age`

Returns the response unchanged when the origin is not allowed.

```
$response = $corsService->addPreflightRequestHeaders($response, $request);
```

---

##### `addActualRequestHeaders(ResponseInterface $response, RequestInterface $request): ResponseInterface`

[](#addactualrequestheadersresponseinterface-response-requestinterface-request-responseinterface)

Injects CORS headers for a real (non-preflight) request:

- `Access-Control-Allow-Origin`
- `Access-Control-Allow-Credentials`
- `Access-Control-Expose-Headers`

```
$response = $corsService->addActualRequestHeaders($response, $request);
```

---

##### `varyHeader(ResponseInterface $response, string $header): ResponseInterface`

[](#varyheaderresponseinterface-response-string-header-responseinterface)

Appends a value to the `Vary` response header without creating duplicates. Used internally to ensure correct caching behaviour when the origin or method varies per-request.

```
$response = $corsService->varyHeader($response, 'Origin');
```

---

##### `getErrors(): array`

[](#geterrors-array)

Returns all CORS errors collected during the current request cycle, keyed by error type (`origin`, `method`, `preflight`, `configuration`, etc.).

```
$errors = $corsService->getErrors();
// ['origin' => "Origin 'http://evil.com' not allowed"]
```

---

##### `hasErrors(): bool`

[](#haserrors-bool)

Returns `true` if any CORS errors have been recorded.

```
if ($corsService->hasErrors()) {
    // inspect or log $corsService->getErrors()
}
```

---

##### `clearErrors(): void`

[](#clearerrors-void)

Resets the internal error collection. Useful when reusing the service across multiple requests in long-running processes.

```
$corsService->clearErrors();
```

---

### `CorsMiddleware`

[](#corsmiddleware)

A PSR-15 middleware that wraps `CorsService` and integrates it into a request/response pipeline.

#### Constructor

[](#constructor-1)

```
new CorsMiddleware(CorsService $cors, array $config = [])
```

Config KeyTypeDefaultDescription`paths` / `allowed_paths``string[]``[]`Paths CORS is applied to. Empty means all paths.`excluded_paths``string[]``[]`Paths where CORS processing is skipped entirely.`handle_preflight``bool``true`Whether to handle `OPTIONS` preflight requests automatically.`handle_errors``bool``true`Whether to catch exceptions and expose errors via header.---

#### `process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface`

[](#processserverrequestinterface-request-requesthandlerinterface-handler-responseinterface)

The PSR-15 entry point. Execution flow:

1. **Skip** — if the request is not a CORS request, or the path is excluded / not in the allowlist, the request passes through to the next handler unchanged.
2. **Preflight** — if `handle_preflight` is `true` and the request is a preflight `OPTIONS`, a complete preflight response is returned immediately (without calling the next handler).
3. **Actual request** — the next handler is called and CORS headers are added to its response.
4. **Error header** — if `handle_errors` is `true` and errors were collected, they are serialised into `X-CORS-Errors` as JSON.
5. **Exception safety** — uncaught exceptions return a `500` response with CORS headers when `handle_errors` is `true`; otherwise they are re-thrown.

---

#### Fluent Setter Methods

[](#fluent-setter-methods)

All setters return `$this` for method chaining.

##### `setPaths(array $paths): self`

[](#setpathsarray-paths-self)

Set (or replace) the list of paths CORS should be applied to.

```
$middleware->setPaths(['api/*', 'webhooks/*']);
```

---

##### `setExcludedPaths(array $paths): self`

[](#setexcludedpathsarray-paths-self)

Set (or replace) the list of paths to exclude from CORS handling.

```
$middleware->setExcludedPaths(['api/internal/*']);
```

---

##### `setHandlePreflight(bool $handle): self`

[](#sethandlepreflightbool-handle-self)

Enable or disable automatic preflight handling.

```
$middleware->setHandlePreflight(false); // delegate to the next handler
```

---

##### `setHandleErrors(bool $handle): self`

[](#sethandleerrorsbool-handle-self)

Enable or disable graceful error handling. When disabled, exceptions propagate normally.

```
$middleware->setHandleErrors(false);
```

---

##### `getCorsService(): CorsService`

[](#getcorsservice-corsservice)

Returns the underlying `CorsService` instance — useful for inspecting errors after a request.

```
$service = $middleware->getCorsService();
$errors  = $service->getErrors();
```

---

Path Matching
-------------

[](#path-matching)

Both `paths` and `excluded_paths` support three matching strategies:

PatternExampleMatchesExact`api/users``/api/users` onlyWildcard `*``api/*``/api/users`, `/api/posts`, …Single char `?``v?/resource``/v1/resource`, `/v2/resource`Paths can also be scoped to a specific hostname by using the hostname as the array key:

```
'paths' => [
    'app.example.com' => ['dashboard/*'],
    'api.example.com' => ['v1/*', 'v2/*'],
],
```

---

Origin Validation
-----------------

[](#origin-validation)

`CorsService` validates the origin format before checking allow-lists:

- Standard URLs are validated with PHP's `FILTER_VALIDATE_URL`.
- `localhost` (with optional port) is accepted without a scheme for local development.
- Invalid origin formats are rejected and recorded in the error log.

Wildcard patterns in `allowedOrigins` (e.g. `https://*.example.com`) are automatically compiled to regex and matched against each incoming request origin.

---

Credential-Aware Enforcement
----------------------------

[](#credential-aware-enforcement)

When `supportsCredentials` is `true`:

- `allowedOrigins` **must not** contain `'*'` — an `InvalidArgumentException` is thrown.
- `exposedHeaders` **must not** contain `'*'`.
- `Access-Control-Allow-Credentials: true` is added to every matched response.

---

Error Handling
--------------

[](#error-handling)

Errors are collected internally rather than silently discarded:

```
$corsService->setDebugMode(true); // also writes to PHP error_log()

// After processing a request:
if ($corsService->hasErrors()) {
    print_r($corsService->getErrors());
}

// Clear for the next request
$corsService->clearErrors();
```

When `handle_errors` is enabled on `CorsMiddleware`, all collected errors are exposed via the `X-CORS-Errors` response header as a JSON string.

---

Advanced Examples
-----------------

[](#advanced-examples)

### Allow all origins (public API)

[](#allow-all-origins-public-api)

```
$corsService = new CorsService([
    'allowedOrigins' => ['*'],
    'allowedMethods' => ['*'],
    'allowedHeaders' => ['*'],
]);
```

### Subdomain wildcard

[](#subdomain-wildcard)

```
$corsService = new CorsService([
    'allowedOrigins'      => ['https://*.example.com'],
    'allowedMethods'      => ['GET', 'POST'],
    'allowedHeaders'      => ['Content-Type', 'Authorization'],
    'supportsCredentials' => true,
]);
```

### Restrict CORS to specific routes

[](#restrict-cors-to-specific-routes)

```
$middleware = new CorsMiddleware($corsService, [
    'paths'            => ['api/*'],
    'excluded_paths'   => ['api/health', 'api/internal/*'],
    'handle_preflight' => true,
    'handle_errors'    => true,
]);
```

### Fluent configuration

[](#fluent-configuration)

```
$middleware = (new CorsMiddleware($corsService))
    ->setPaths(['api/*'])
    ->setExcludedPaths(['api/internal/*'])
    ->setHandlePreflight(true)
    ->setHandleErrors(true);
```

### Standalone usage (without a middleware stack)

[](#standalone-usage-without-a-middleware-stack)

```
use Effectra\Cors\CorsService;

$corsService = new CorsService([
    'allowedOrigins' => ['https://frontend.example.com'],
    'allowedMethods' => ['GET', 'POST'],
    'allowedHeaders' => ['Content-Type'],
]);

if ($corsService->isPreflightRequest($request)) {
    return $corsService->handlePreflightRequest($request);
}

if ($corsService->isCorsRequest($request)) {
    $response = $corsService->addActualRequestHeaders($response, $request);
}
```

---

Response Headers Reference
--------------------------

[](#response-headers-reference)

HeaderSet byCondition`Access-Control-Allow-Origin`Preflight &amp; actual requestOrigin is allowed`Access-Control-Allow-Credentials`Preflight &amp; actual request`supportsCredentials` is `true``Access-Control-Allow-Methods`Preflight onlyOrigin is allowed`Access-Control-Allow-Headers`Preflight onlyOrigin is allowed`Access-Control-Max-Age`Preflight only`maxAge` is not `null``Access-Control-Expose-Headers`Actual request`exposedHeaders` is non-empty`Vary`BothDynamic origin or method matching is active`X-CORS-Errors`MiddlewareErrors collected and `handle_errors` is enabled---

Contributing
------------

[](#contributing)

Contributions are welcome! Please open an issue or submit a pull request. For major changes, open an issue first to discuss what you would like to change.

---

License
-------

[](#license)

The effectra/cors package is open-sourced software licensed under the [MIT license](LICENSE).

###  Health Score

30

—

LowBetter than 61% of packages

Maintenance53

Moderate activity, may be stable

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~933 days

Total

2

Last Release

22d ago

Major Versions

v1.0.0 → v2.0.02026-07-26

### Community

Maintainers

![](https://www.gravatar.com/avatar/7e6219ae87e98df8783b2f595b013035dd183c0712afd24045a8acf0a40c3bdf?d=identicon)[effectra](/maintainers/effectra)

---

Top Contributors

[![BMTmohammedtaha](https://avatars.githubusercontent.com/u/95439605?v=4)](https://github.com/BMTmohammedtaha "BMTmohammedtaha (2 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/effectra-cors/health.svg)

```
[![Health](https://phpackages.com/badges/effectra-cors/health.svg)](https://phpackages.com/packages/effectra-cors)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.9k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[cakephp/authentication

Authentication plugin for CakePHP

1214.3M118](/packages/cakephp-authentication)[typo3/cms-core

TYPO3 CMS Core

3313.6M5.6k](/packages/typo3-cms-core)[sunrise/http-router

A powerful solution as the foundation of your project.

16852.3k12](/packages/sunrise-http-router)[typo3/cms-adminpanel

TYPO3 CMS Admin Panel - The Admin Panel displays information about your site in the frontend and contains a range of metrics including debug and caching information.

115.8M71](/packages/typo3-cms-adminpanel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
