# ADR-003: CORS Preflight Returns 204 No Content **Status:** Accepted **Date:** 2026-03-18 ## Context CORS preflight requests use the HTTP `OPTIONS` method. Browsers send them before cross-origin requests that carry non-simple headers or methods, and they expect a response with `Access-Control-Allow-*` headers. The response status code of a preflight is not consumed by the browser's fetch algorithm — only the headers matter — but the choice of status code affects certain clients and proxies. Common practice uses either 200 OK or 204 No Content for OPTIONS responses. RFC 9110 specifies that 204 indicates "the server has successfully fulfilled the request and there is no additional content to send". This is semantically more accurate for a preflight: there is no payload, only permission headers. ## Decision The `CORS` middleware checks `r.Method == http.MethodOptions` after writing the CORS headers. If the method is OPTIONS, it calls `w.WriteHeader(http.StatusNoContent)` and returns immediately without calling `next.ServeHTTP`. The downstream handler is never reached for preflight requests. Non-OPTIONS requests that pass the origin check continue to `next` with the `Access-Control-Allow-Origin` and related headers already set on the response. If the request origin is not in the allowed list, CORS headers are not set and the request continues to `next` unchanged — the browser will block the response at its end, but the server does not return an explicit rejection. ## Consequences - 204 is semantically cleaner than 200 for no-body responses, and avoids some proxy caches treating the OPTIONS response as cacheable content. - Some legacy clients or API testing tools expect 200 for OPTIONS. If this is a concern, the response status can be changed at the application level by wrapping the `CORS` middleware, but 204 is the default and documented contract. - The `allowedMethods` constant (`GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS`) and `allowedHeaders` constant (`Content-Type, Authorization, X-Request-ID`) are package-level constants, not configurable per route. Applications with fine-grained per-route CORS requirements should configure their router's CORS support instead. - For local development, passing `[]string{"*"}` as `origins` is supported: any origin header is treated as allowed, and CORS headers are set with the actual origin value (not `*`) to support credentials if needed.