Severity: high — the new middleware is unusable for the canonical CORS use-case (cross-origin POST/PUT/PATCH/DELETE) without an upstream fix.
Surfaced by: titan Phase 2.6 (Wheels 4.0 upgrade), 2026-05-15. The legacy set(allowCorsRequests=true) global path handled preflight; the new middleware cannot.
Repro
Configure a Wheels 4.0 app with no explicit OPTIONS route on /api/v1/*:
set(middleware = [
new wheels.middleware.Cors(allowOrigins="https://portal.pai.com")
]);
Then:
OPTIONS /api/v1/jvm HTTP/1.1
Origin: https://portal.pai.com
Access-Control-Request-Method: POST
→ HTTP/1.1 404 with Wheels.RouteNotFound — Incorrect HTTP Verb for route (from Dispatch.cfc:214).
Expected: 204 No Content with Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers. Browsers will block the actual cross-origin request when preflight fails.
Root cause
In vendor/wheels/Dispatch.cfc:$request():
- Line 260:
$paramParser() runs
- Line 260+:
$findMatchingRoute() 404s when no OPTIONS route matches
- Line 320: middleware pipeline is built — never reached on the OPTIONS path
wheels.middleware.Cors has explicit preflight-handling code at Cors.cfc:89-95 that is unreachable.
For comparison, the legacy set(allowCorsRequests=true) path aborts OPTIONS at EventMethods.cfc:255-262, BEFORE dispatch route-matching.
Suggested fix
One of:
- Run a pre-route middleware pass (CORS middleware specifically — or a designated subset — runs before
$findMatchingRoute()).
- Auto-register an OPTIONS verb on every route added via
.resources() and friends, dispatching to a built-in preflight handler when CORS middleware is present.
- Special-case OPTIONS in the dispatcher: if the middleware pipeline contains a
wheels.middleware.Cors instance, route OPTIONS to its preflight handler before 404'ing.
Why this matters for v4.0.1
This is the entire reason apps reach for a CORS middleware over the legacy global setting. Without preflight handling, the middleware is strictly less capable than the 3.x setting it's meant to replace.
cc @bpamiri
Severity: high — the new middleware is unusable for the canonical CORS use-case (cross-origin POST/PUT/PATCH/DELETE) without an upstream fix.
Surfaced by: titan Phase 2.6 (Wheels 4.0 upgrade), 2026-05-15. The legacy
set(allowCorsRequests=true)global path handled preflight; the new middleware cannot.Repro
Configure a Wheels 4.0 app with no explicit OPTIONS route on
/api/v1/*:set(middleware = [ new wheels.middleware.Cors(allowOrigins="https://portal.pai.com") ]);Then:
→
HTTP/1.1 404withWheels.RouteNotFound — Incorrect HTTP Verb for route(fromDispatch.cfc:214).Expected:
204 No ContentwithAccess-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers. Browsers will block the actual cross-origin request when preflight fails.Root cause
In
vendor/wheels/Dispatch.cfc:$request():$paramParser()runs$findMatchingRoute()404s when no OPTIONS route matcheswheels.middleware.Corshas explicit preflight-handling code atCors.cfc:89-95that is unreachable.For comparison, the legacy
set(allowCorsRequests=true)path aborts OPTIONS atEventMethods.cfc:255-262, BEFORE dispatch route-matching.Suggested fix
One of:
$findMatchingRoute())..resources()and friends, dispatching to a built-in preflight handler when CORS middleware is present.wheels.middleware.Corsinstance, route OPTIONS to its preflight handler before 404'ing.Why this matters for v4.0.1
This is the entire reason apps reach for a CORS middleware over the legacy global setting. Without preflight handling, the middleware is strictly less capable than the 3.x setting it's meant to replace.
cc @bpamiri