DaloyJS OpenAPI spec
DaloyJS emits a clean OpenAPI 3.1 document straight from your route definitions, no plugins, no separate decorators. Validation, types, and the spec all share one source of truth.
One line: auto-mount /docs, /openapi.json, /openapi.yaml
FastAPI-style. Pass docs: true to the App constructor and DaloyJS registers GET /openapi.json + GET /openapi.yaml (the live spec in both formats) and GET /docs (a Scalar API reference UI) for you.
Use docs: "auto" to mount only when the App is not in production (the create-daloy templates default to this, so a deployed API does not publish its schema), or leave it off (the default) and mount manually with the helpers below. Customize paths, UI, and tags via the object form. Since 1.5.5 the object form behaves like "auto" unless you set enabled, so customizing the UI never publishes your route map in production by accident; set enabled: true for a public API that should serve its docs in production:
Pick a UI: Scalar, Swagger UI, or Redoc
Set ui to "scalar" (default), "swagger", or "redoc". All three render the same live spec, mount on the same paths, and ship the same strict CSP and CDN-hosted assets, so switching is a one-word change. Scalar and Swagger UI include a developer request console for protected routes. Redoc is a read-only reference UI: it displays security requirements, but it does not ship a built-in Try it console.
Swagger UI keeps authorizations from its Authorize dialog across reloads by default. Scalar automatically selects the first configured OpenAPI security scheme, so the auth form is ready for developers to paste a bearer token or API key. If you want a different Scalar default, set it explicitly:
If your docs need a visible login entry point, add docs.auth. The same button is rendered for Scalar, Swagger UI, and Redoc. Point it at a local login form, Better Auth route, Clerk sign-in URL, or an OIDC authorization URL from Auth0, Okta, Microsoft Entra ID, Cognito, Keycloak, or another provider:
The redoc object is forwarded verbatim to Redoc.init(specUrl, configuration, element). Because Redoc builds its search index in a blob: Web Worker, the auto-mounted /docs route automatically widens that page's CSP with worker-src 'self' blob: for ui: "redoc" only. Scalar and Swagger UI keep the tighter policy. The scalar option is ignored unless ui is "scalar", and likewise for swagger and redoc.
The scalar object is forwarded to Scalar's HTML API as JSON configuration while Daloy keeps the live openapiPath as the source. Use it for themes, custom CSS, layout, auth defaults, and client visibility without copying the docs HTML.
Advanced: generate the spec manually
Need the raw spec object (for codegen, contract tests, or a custom route)? Call generateOpenAPI(app, options) directly:
Advanced: serve docs from your own route
swaggerUiHtml, scalarHtml, and redocHtml all return self-contained HTML pages that load their assets from jsDelivr with a strict CSP allowing only that origin. When you hand-roll the route with redocHtml, pass allowBlobWorkers: true to htmlResponse (or docsContentSecurityPolicy) so Redoc's blob: search worker is allowed by the CSP:
If you want to test your docs UX against a much larger contract, see the large fake REST demo. It is a better benchmark than a toy CRUD sample when you need to validate search, grouping, and render performance.
Dump to disk for codegen
What gets emitted
- One
operationIdper route, duplicates throw at registration. - Path params
:idnormalized to{id}. - Schema bodies converted via
schema.toJSONSchema?.()when supported, or a structural fallback. - Reusable
components.schemas.Problemfor RFC 9457 errors. tags,summary,description, and per-statusdescription.
Webhooks
OpenAPI 3.1 lets a producer publish top-level webhooks, operations a consumer is expected to implement. Pass webhooks to generateOpenAPI and DaloyJS emits them under the document's top-level webhooks map.
Callbacks
Callbacks describe out-of-band requests that an operation may trigger on the consumer (e.g. a subscription endpoint that later POSTs to the URL the caller supplied). Attach a callbacks map directly to a route or webhook.
Each callback name maps to one or more runtime expression keys (e.g. "{$request.body#/callbackUrl}"), each of which maps to one or more operations keyed by HTTP method. Empty maps and empty arrays are skipped, passing an empty callback never produces a malformed spec.
Discriminated unions
OpenAPI 3.1's discriminator is the canonical way to describe tagged unions. DaloyJS ships two helpers from @daloyjs/core/openapi (and the root package):
discriminator(propertyName, mapping?): the bare spec builder. Use it when you already have a hand-rolled JSON Schema and want to attach the field cleanly.discriminatedUnion(propertyName, variants, opts?): a Standard-Schema- compatible wrapper that both validates at runtime (dispatching on the discriminator value) and exposes.toJSONSchema()so the OpenAPI generator emits{ oneOf, discriminator }automatically.
At runtime the wrapper rejects non-objects, missing or non-string discriminators, and unknown discriminator values with a clear Standard Schema issue, then defers to the matching variant's validator for everything else.