CORS
app.useCors(policy) enables Cross-Origin Resource Sharing for routes registered after the call. app.cors(policy) is an alias. It also auto-registers an OPTIONS preflight route for each subsequent path.
import { Sloppy } from "sloppy";
const app = Sloppy.create();
app.useCors({
origins: ["https://app.example.com"],
credentials: true,
methods: ["GET", "POST"],
headers: ["content-type", "x-request-id"],
exposedHeaders: ["x-trace-id"],
maxAgeSeconds: 600,
});
app.get("/users", listUsers);
app.post("/users", createUser);Policy shape
| Field | Default | Behavior |
|---|---|---|
origins | — | Allowed origins. Either an array of explicit origins or ["*"]. Required. |
credentials | false | Send Access-Control-Allow-Credentials: true. Cannot combine with "*" origin. |
methods | [] | Methods exposed to preflight. Empty falls back to the methods registered for the path. |
headers | [] | Allowed request headers; sent in Access-Control-Allow-Headers. |
exposedHeaders | [] | Sent in Access-Control-Expose-Headers on actual responses. |
maxAgeSeconds | unset | Non-negative integer for Access-Control-Max-Age. |
origins, methods, headers, and exposedHeaders accept a single string or an array. origin, allowHeaders, exposeHeaders, and maxAge are accepted as aliases.
origins: ["*"] allows any origin but rejects credentials: true — the combination is invalid per the CORS spec.
What it adds to responses
For an allowed origin, every wrapped route response gets:
Access-Control-Allow-Origin— echoed origin, or*for the wildcard policy.Vary: Origin— set when the policy uses explicit origins (varies per origin).Access-Control-Allow-Credentials: true— only whencredentials: true.Access-Control-Expose-Headers— only whenexposedHeadersis non-empty.
Preflight (OPTIONS)
Each subsequent route registration also installs an OPTIONS route at the same path. The preflight handler answers 204 with:
Access-Control-Allow-OriginAccess-Control-Allow-Methods— the methods registered on this path under the current policy (orpolicy.methodsif explicitly listed).Access-Control-Allow-Headers—policy.headers, when non-empty.Access-Control-Max-Age—policy.maxAgeSeconds, when set.Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers— when origins are explicit.
A preflight request with an origin that isn't allowed, a method that isn't registered, or a header outside policy.headers answers 403.
If you re-register a route under a different policy on the same path, the auto-registered preflight throws — install the policy once per path.
Scope
useCors applies to routes registered after it. Route groups and controller mappers see the current app policy at the time each child route registers, so this is fine:
app.useCors({ origins: ["https://app.example.com"] });
app.mapController("/users", UsersController, (users) => {
users.get("/", "list");
users.get("/{id:int}", "get");
});Status
CORS preflight and response-header injection run in the bootstrap app-host handler path. Compiler source input supports literal app.useCors({...}) policies, emits Plan-level CORS metadata, and adds generated OPTIONS preflight routes to emitted artifacts. Dynamic policy objects are rejected with SLOPPYC_E_UNSUPPORTED_CORS instead of being silently dropped.