CORS

Set up cross-origin access for public APIs or authenticated frontends with support for multiple origins and dynamic closures.

CORS is built into Kirby core. Configure it directly in your config.php.

See Kirby's cors option reference for every setting and its default.

Minimal Setup (Public API)

Enable CORS with Kirby's defaults – wildcard origin (*), standard HTTP methods, and no credentials – and allow the headers a request asks for:

site/config/config.php
return [
    'cors' => [
        'allowHeaders' => true
    ]
];

'cors' => true alone sends no Access-Control-Allow-Headers, so the browser stops every request with a header beyond the basic ones at preflight: Content-Type: application/json on a KQL query, Authorization, X-Language, or X-Cacheable.

Headless CMS With Authentication

For headless setups with bearer token authentication:

site/config/config.php
return [
    'cors' => [
        'allowOrigin' => 'https://example.com',
        'allowMethods' => ['GET', 'POST', 'PATCH', 'DELETE'],
        'allowHeaders' => true
    ]
];

A bearer token travels in the Authorization header, which allowHeaders covers, so allowCredentials stays off.

allowCredentials lets browsers include cookies and HTTP authentication with cross-origin requests. Only a frontend that relies on Kirby's session cookie needs it, and only for an origin you fully trust and control. Every origin you allow can read your API from a visitor's browser, so keep origins, methods, and headers down to what your frontend needs, and reach for the wildcard only on an API that is public anyway.

Multiple Frontend Apps

Allow multiple origins with specific configuration:

site/config/config.php
return [
    'cors' => [
        'allowOrigin' => [
            'https://app.example.com',
            'https://admin.example.com'
        ],
        'allowHeaders' => [
            'Authorization',
            'Content-Type',
            'X-Language',
            'X-Cacheable'
        ]
    ]
];

Dynamic CORS Configuration

For request-based CORS configuration, use a closure:

site/config/config.php
return [
    'cors' => function ($kirby) {
        $origin = $kirby->request()->header('Origin');

        // Allow specific origins with credentials
        if (in_array($origin, ['https://app1.com', 'https://app2.com'])) {
            return [
                'allowOrigin' => $origin,
                'allowCredentials' => true,
                'allowMethods' => ['GET', 'POST']
            ];
        }

        // Fallback to wildcard for other origins
        return ['allowOrigin' => '*'];
    }
];