CORS
Letting a browser frontend on another origin read your content.
A headless CMS is consumed by frontends that do not share its origin, so the API sends CORS headers by default. The default is deliberately the permissive one, because for published content it gives away nothing:
Access-Control-Allow-Origin: *with credentials off.
Anonymous callers can only ever read published entries. Drafts return 404 by
id or by slug, and list queries are forced to a published-only filter, so a
wildcard exposes exactly what curl could already fetch and nothing more.
Because the wildcard is present, browsers refuse to attach cookies, so no
admin session can be ridden from another origin.
Configuration
Pass a cors option to createApp:
import { createApp } from "@opencms/api";
const app = createApp({
data,
auth,
cors: {
origin: ["https://example.com", "https://www.example.com"],
credentials: true,
},
});| Option | Default | Meaning |
|---|---|---|
origin |
"*" |
"*", one origin, an array of origins, or a predicate (origin) => boolean. |
credentials |
false |
Allow cookies cross-origin. Ignored when origin is "*". |
allowHeaders |
content-type, x-api-key, authorization |
Request headers a browser may send. |
allowMethods |
GET, HEAD, POST, PATCH, PUT, DELETE, OPTIONS |
Methods a browser may use. |
exposeHeaders |
none | Response headers a browser may read beyond the safelisted set. |
maxAge |
86400 |
Preflight cache lifetime, in seconds. |
Pass cors: false to emit no CORS headers at all, which restores the
same-origin-only behaviour of releases before this option existed.
Sending cookies cross-origin
Two things must line up, and forgetting the second is the usual cause of a confusing failure:
cors: { origin: [...], credentials: true }oncreateApp. The wildcard cannot be combined with credentials, and this module will dropcredentialsrather than silently narrowing the origin for you.- The same origins passed to
createAuth({ trustedOrigins: [...] }). better-auth enforces its own CSRF check on/api/auth/*and will reject the request before any CORS header matters.
For machine clients, prefer an API key in x-api-key over cross-origin
cookies. It sidesteps CSRF entirely and is what the API is designed around.
Behaviour worth knowing
- A request with no
Originheader passes through untouched. It is same-origin or not from a browser, and CORS has nothing to say about it. - A disallowed origin gets no CORS headers rather than an error status. The request still executes; the browser is what blocks the response. This is how CORS is specified to work.
- A preflight always returns 204, allowed or not, so a rejection surfaces as a CORS error in the console rather than a misleading 404.
- When
originis not the wildcard, responses carryVary: Originso a shared cache cannot serve one origin's response to another.