OpenAPI Documentation
VextJS has built-in automatic generation of OpenAPI documentation. Based on route validate and docs configuration, the framework generates an OpenAPI 3.0 JSON document and serves the default /docs page with the Vext Docs Renderer. Third-party documentation tools should consume /openapi.json directly.
Read by task
Start with Quick Start, then consult configuration snippets as needed. A documentation manifest is a generated inventory of entries/sources for indexing. Static projection reads declarations to generate descriptions; it does not execute business code or replace runtime verification.
Quick Start
Prerequisite: a TypeScript project with dev, build, and start scripts from Quick Start. The next two files form a standalone example; merge the configuration into an existing project. User data stays in memory for this process and disappears after restart. This example verifies documentation generation; it provides no database, authentication, or email uniqueness checks.
1. Enable OpenAPI
Enable openapi.enabled in configuration:
2. Add documentation to a route
3. Open the documentation
Run npm run dev. After the ready message, open:
The JSON should contain GET and POST /users; GET page and limit are integers and POST name and email are required. The UI should show “List users” and “Create a user” with expandable parameter and response details.
4. Verify requests and the production build
From another terminal (these commands work in Windows PowerShell):
Expect 201 with nonempty data.id, then 200 with data.total: 1, then 422 for email and 422 for a fractional page. PowerShell reports HTTP errors for the last two commands; they do not add users. Stop dev with Ctrl+C, run npm run build -- --typecheck and npm start, and check both documentation URLs and requests again. The new process starts with no users.
Later configuration and route snippets are independent. Your application supplies app, handler, and business services; do not execute object fragments as whole files or register every same-path snippet in one app. See Validation for runtime rules and Security for authentication wiring.
Reading the docs UI and multi-source surfaces
The default Vext Docs UI keeps HTTP API, Pages, Services, Utils, Models, discovered Components, Plugins, and Middlewares as top-level sections. Active top-level sections can collapse and expand their current navigation tree.
The HTTP API and Pages sections:
- use recursive navigation generated from OpenAPI path segments;
- keep stable resource segments such as
/api/v1/infoas categories; - use each operation
summaryas the concrete leaf label with endpoint fallback; - weaken dynamic path parameters such as
{id}instead of treating them as normal business categories.
The operation view shows response status codes as horizontal tabs without repeated panel headings, expands local schema $ref values into real fields, and avoids artificial root rows for object schemas.
The desktop sidebar stays sticky while the content scrolls, can auto-size to visible navigation labels, supports persisted manual resizing, and built-in docs assets are version-tagged so browser cache does not hide renderer updates.
The header separates search, UI controls, filters, and Authorize into clear rows, while the Overview workspace shows counts plus package startup/build/verification commands. Right-side API/code/model/plugin/middleware entries use a separated item shell so long pages remain scannable.
Pages are detected from route handlers that call res.render() or res.renderError(). Services / Utils / Components are generated from standard JSDoc without importing user code.
Models are listed from recognizable model files even when no JSDoc is present; root-level models are shown directly under Models instead of under an artificial root group, and nested model files are grouped by source directory.
The renderer statically reads supported model definition shapes to show registry key, name, collection, connection, schema fields, enums, options, indexes, methods, hooks, and usage without importing or executing model code.
Plugins and middlewares are scanned from src/plugins and src/middlewares to show JSDoc, lifecycle/bootstrap metadata, app extensions, middleware type, route usage, and source links when they can be inferred from source text.
Locales, Config, Styles, and Preload static source docs are optional advanced sources that can be enabled explicitly through openapi.docs.code.*; they are not part of the default top-level documentation surface.
On local loopback docs pages, code docs entries can include an Open source link that redirects to vscode://file/...; the link is hidden for non-local access.
Route-level docs.tags is deprecated and ignored with a warning; operation tags are inferred automatically from route path/source and are tucked into collapsed Metadata instead of being shown as primary badges.
x-tagGroups is emitted only when openapi.tagGroups is explicitly configured as a raw OpenAPI vendor extension; the built-in docs navigation does not depend on it. When OpenAPI security schemes are present, the UI shows operation security badges and a global Authorize control that is merged into same-origin Try it out requests.
The docs UI offers theme and density controls, an Overview workspace, keyboard search shortcuts, category filters, highlighted matches, a desktop outline, copy buttons for endpoints, links, responses, usage snippets, and source paths, plus navigation deep links. Middle dynamic path parameters remain visually subdued but keep their hierarchy when they lead to stable child resources, as in /docs-nav/{id}/sdfs/sdfaf.
Try it out is a request console. Each operation can show a server selector and full URL preview with Copy URL, plus Params, Headers, Body, Samples, History, and Response tabs. Query and header rows stay compact when empty and support raw fallbacks; header rows come from OpenAPI parameters[in=header], including validate.header. The Headers tab shows auth state and effective headers so Authorize injection and manual overrides can be checked together. Samples include cURL, browser fetch, Node fetch, and Axios snippets. The fixed Response tab offers pretty/raw body views and shows actual sent request headers beside response headers. Axios is sample text only, not a Vext runtime dependency.
On small screens and large APIs, mobile uses a navigation drawer with synchronized search and category filters. Generated field tables become labeled row cards at narrow widths. Try it out internals are created when an operation console opens; long HTTP API lists render incrementally with Load more while keeping deep-link targets reachable.
For multiple versions, if generated OpenAPI paths contain at least two source groups such as /api/v1/**, /api/v2/**, /api/beta/**, /v1/**, /v2/**, or /beta/**, Vext Docs shows an ordered All / API v1 / API v2 / API Beta selector. Numbered versions precede named channels such as alpha, beta, or rc.
Each source fetches filtered /_vext/docs/openapi.json?source=<id>, code.json?source=<id>, and search.json?source=<id> data, so the current source has its own Overview counts, navigation tree, search state, access-filtered operations, and deep links.
Non-All sources return only OpenAPI entries by default; Code JSDoc items appear for a source only when that source explicitly configures code.include / code.exclude. Existing single-source #anchor links remain valid; multi-source links use #source=<id>&view=<view>&id=<anchor>.
Projects can define custom source surfaces with openapi.docs.sources when automatic version detection is not enough. source.access, including source.access.visible, is applied to the source selector and source-aware endpoints. Every explicit source still needs a match pattern because it scopes OpenAPI data. For a code-only source, use a stable non-API namespace such as /sdk/** and opt into Code JSDoc with code.include / code.exclude.
Try it out renders OpenAPI servers[].variables beside the server selector and applies them to URL preview, Copy URL, samples, history, and Send.
Projects can optionally configure a browser-side request hook with openapi.docs.tryItOut.hookScript and hookGlobal. hookGlobal is only the lookup name, so hook notes are shown only when a hook script is configured or the runtime global exposes beforeRequest / afterResponse.
The docs page calls those hook functions around fetch, merges returned request headers/body/URL changes, and shows diagnostics in the Response tab. Hooks run only in the browser documentation page and Vext does not import or execute backend project code for them.
Multi-source configuration
Use openapi.docs.sources when Public/Admin/Internal, version, or audience boundaries cannot be inferred from the path alone:
source.access is passed to openapi.docs.access.resolver as a kind: "source" descriptor. source.access.visible: false hides the source before the resolver runs. This source filtering is separate from operation filtering.
options.docs.access is emitted as x-vext-docs-access. To apply operation visible: false, tryItOut: false, and resolver decisions in the docs UI, set openapi.docs.access.mode to "visibility-only" or "enforce"; the default "off" does not filter operations. Once enabled, the resolver receives a kind: "operation" descriptor with its access field. Hiding an entry or disabling Try it out does not change the actual API's access control. See “Control by environment” for the difference between these modes at canonical /openapi.json.
source.code.include / source.code.exclude opt a non-All source into Code JSDoc items; otherwise it exposes only OpenAPI entries. Code filters match item ID, title, and source file, so patterns such as models/* and services/sdk/** can scope source files.
Try it out request hook
hookScript points to a browser script loaded by the docs page. The script should expose window[hookGlobal] and may implement beforeRequest / afterResponse:
If you need to append organization-level extension fields after generation, you can use OpenAPI hooks. OpenAPIGenerator.generate() remains synchronized, and openapi:afterGenerate must also return patches synchronously:
Job docs source
Vext Docs can include Job entries when openapi.docs.code.jobs is enabled. These entries are separate from OpenAPI operations because jobs are not HTTP endpoints. The source inherits jobs.dir/include/exclude; explicit Docs fields override individually, and disabled definitions remain visible. Details show schedule units, timezones, switches and static parse state without treating unknown fields as defaults or proving runtime execution. See Scheduled Jobs for configuration and JSDoc examples.
Document configuration
Global configuration
Configure OpenAPI global information in config/default.ts:
apiKey schemes may use in: "cookie", and validate.cookie is rendered as OpenAPI cookie parameters. Browser Try it out cannot set the forbidden Cookie header directly; use same-origin browser cookies or an HTTP client such as cURL when you need manual cookie values.
Code docs scan src/services, src/utils, the configured models directory, src/frontend/components, src/plugins, and src/middlewares without importing or executing user code. Services, utils, and components require standard JSDoc on exported symbols. Models are listed from recognizable model files even without JSDoc, and JSDoc above the default export enriches the generated entry. Supported model definitions also expose schema fields, enums, options, indexes, methods, hooks, and a usage snippet in the default UI. Plugins expose inferred plugin name, dependencies, lifecycle hooks, global middleware registration, app extensions, and setup usage. Middlewares expose inferred middleware/factory type and route usage snippets. When vext start runs built output, Vext prefers <project>/src if source files are present so top-level JSDoc and local source links are preserved; if the source tree is not deployed, it falls back to the runtime directory.
Routing level document configuration
Each route can configure its OpenAPI documentation information through options.docs:
x-rate-limit is generated automatically only when the rate-limit object middleware provides positive numeric max and window options. String middleware references, missing options, partial options, and malformed option values do not emit an empty x-rate-limit, so OpenAPI consumers are not given a misleading rate-limit contract.
docs Configuration details
summary — interface summary
One sentence describing the interface function, displayed in the interface list of the document UI:
description — Detailed description
Detailed description of support for Markdown format is displayed when the interface is expanded:
tags — deprecated operation tags
Route-level docs.tags is deprecated and ignored. Vext now infers one operation tag automatically from the route path, falling back to the source file only when needed:
If an existing route still sets docs.tags, Vext ignores that value and logs a deprecation warning. Remove the field from route definitions and rely on automatic path/source inference.
operationId — operation identification
Globally unique operation identifier. If not specified, the framework automatically infers:
operationId must remain unique across the whole OpenAPI document. During generation, Vext validates both explicit docs.operationId values and inferred values: duplicate explicit values, explicit values that collide with inferred values, or different routes that infer the same value all fail generation. Fix the conflict by setting a unique docs.operationId on one route or by changing the route method/path so inferred values differ.
hidden — hide route
Routes you don't want to appear in the document (such as internal interfaces):
hidden removes the entry from generated documentation only. The real route remains callable and still needs access control.
deprecated — Mark deprecated
Mark the interface as deprecated, and there will be a strikethrough and deprecation prompt in the document:
security — security solution
For new applications, declare route protection with RouteOptions.auth. OpenAPI security is generated from auth before the legacy middleware-name fallback:
Route metadata is statically projected without executing route modules. Keep the authentication fields in the final inline options object, or in a same-file const passed directly to the route call; an options helper call is rejected by the finite static grammar.
Use auth: { security: "bearerAuth" } to choose a scheme explicitly. If auth.security is absent, auth: { required: false } without roles, scopes, permissions, or check projects OpenAPI security: []. Otherwise, the explicit scheme takes priority and the fallback is bearerAuth. Roles, scopes, permissions, or check still require authentication at runtime; auth.security alone only affects documentation. Higher-priority docs.security can override the documentation result without changing runtime checks. config.openapi.guardSecurityMap remains for legacy middleware-only routes, not as the main path for new Auth examples.
Keep runtime authorization, OpenAPI security, and Docs access separate
auth.scopes is a runtime predicate against req.auth.scopes; it is not automatically copied into OAuth scopes. When an OpenAPI consumer must see OAuth scopes, declare them explicitly, for example auth: { scopes: ["posts:write"], security: [{ oauth2: ["posts:write"] }] }. Use docs.access only to describe or filter the documentation audience; keep the route's auth requirement in place even when an operation is hidden from Docs.
Manual override:
responses — runtime response contracts and docs metadata
Declare the JSON response shape in top-level RouteOptions.responses, beside
validate and docs. This is the runtime source of truth: Vext uses it for
wire serialization, OpenAPI, the route manifest, and generated frontend client
types. Use docs.responses only for descriptions, examples, headers, and
content type metadata.
Runtime selectors may be exact status codes (200), status families (2xx),
or default. After response:before finishes, Vext selects the final status in
exact → family → default order. Each schema is compiled once when the route is
registered with fast-json-stringify; request handling does not compile it
again. Undeclared object fields are removed recursively, and missing required
fields fail before response bytes are committed.
The schema describes the business data passed to res.json(). When the normal
Vext response envelope is enabled, Vext also compiles the surrounding
{ code, data, requestId } shape. You may use schema-dsl field maps or a
self-contained raw JSON Schema object. A standalone unresolved $ref cannot be
compiled; include its $defs in the same schema.
docs.responses.<selector>.schema remains supported for documentation-only
compatibility. It uses JSON.stringify at runtime and does not project fields
or provide the compiled-serialization performance path. Do not declare a
schema in both locations for the same normalized selector; route registration
fails instead of choosing one silently.
HEAD routes and exact 204 contracts never compile or emit a body.
rawJson(), text(), redirects, files/downloads, streams, and HTML/SSR
render() responses intentionally bypass the JSON contract serializer.
The 4xx schema above applies to business data sent with res.json(data, 4xx). Errors from app.throw() go through the global error handler and are not rewritten by that route schema. A documented response example does not set the runtime status, body, or headers.
Response example
Multiple response example
Custom Content-Type
contentType is documentation metadata. Runtime compiled response schemas are
JSON-only; use text(), file/download, stream, or another matching response
method for non-JSON payloads.
Response header
Automatic linkage between validate and document
The validate rule in the route is automatically mapped to the OpenAPI document, no need to write it again:
Automatically generated OpenAPI parameters:
Rules for validate.body are automatically mapped to requestBody (JSON schema):
Generated requestBody schema:
Field-level business descriptions use the explicit side-effect-free builder, and the generator outputs them as OpenAPI schema description values:
The generated requestBody schema will contain:
File upload routing (multipart/form-data)
Use RouteOptions.multipart.files to declare the file upload route, and the generator automatically outputs multipart/form-data requestBody.
Generated OpenAPI snippet:
required: true is an OpenAPI hint and participates in runtime checks when multipart parsing is enabled and the request is multipart. A missing required field returns 400. It does not guarantee rejection of a non-multipart request; handlers must still check req.files and the expected field. Optional and undeclared fields are not restricted by the file-field allowlist, though file count, size, and MIME limits still apply. See Uploads for reading and failure checks.
When both are declared, multipart.files takes priority for OpenAPI requestBody projection. They are not mutually exclusive at runtime: validate.body may still run. The built-in multipart parser does not put text parts in req.body, so do not assume form text will validate as a JSON body.
Control by environment
It is recommended to enable documentation in the development environment and turn it off in the production environment:
If production needs API documentation but should hide the page's interactive request control, merge this configuration:
ui.tryItOut: false hides interaction from the docs page; other clients can still call the API. visibility-only keeps canonical /openapi.json complete while docs data follows actual access.visible and resolver decisions. Setting only the mode does not identify an audience automatically. Use enforce with matching decisions if canonical OpenAPI must be filtered too. Runtime APIs still need independent authentication and authorization.
Custom document path
Modify the registration paths of the two endpoints through docs.path and jsonPath. docsPath remains as a compatibility field, but new projects should prefer docs.path:
Reverse proxy path prefix scenario
When an application is deployed on a reverse proxy, it needs to be handled in two situations depending on whether the proxy strips the prefix.
Case 1: Proxy stripping prefix (proxy_pass with / at the end)
At this time, the request path received by vext has removed /admin, and route registration does not need to change. The built-in docs page fetches source-aware data from /_vext/docs/*.json, so those browser-facing asset/data URLs also need the public /admin prefix. jsonPublicPath is still useful as the public canonical OpenAPI URL for external tools and metadata, but it is not the primary data endpoint used by the built-in source-aware docs UI.
Use docs.assetsPublicPath for browser-facing docs assets/data, while keeping docs.assetsPath as the internal route prefix registered by vext:
Request link:
Scenario 2: Preserve the prefix (proxy_pass has no trailing /)
At this time, the request path received by vext still contains /admin, and the endpoint paths need to be configured synchronously. There is no need to configure assetsPublicPath or jsonPublicPath because the browser-facing and internal paths are the same:
Comparison of two situations
servers — document interaction address
servers is a metadata field written to the OpenAPI specification document itself, independent of the endpoint registration path. Documentation UIs and third-party tools can use it as the base address for interactive requests.
Default behavior (when not configured):
The relative path / will automatically follow the domain name of the current page, and the default value is sufficient in most cases.
Scenarios that require explicit configuration:
- The documentation page and the API are not in the same domain (cross-domain)
- You want documentation UIs or third-party tools to expose environment switching
After configuration, documentation UIs or tools that support servers can let users switch the target environment.
Vext Docs uses these servers[] entries as the Try it out server list and defaults to the first valid server. For fixed local or deployed endpoints, configure the complete URL including the port, such as http://127.0.0.1:3000. Reserve servers[].variables for genuinely variable parts such as environment names, regions, tenants, or API versions; when present, they are rendered as editable controls and applied to URL preview, Copy URL, code samples, and Send requests. The Same origin option is shown automatically only when no OpenAPI servers are configured; set openapi.docs.tryItOut.sameOrigin to true or false to force the behavior. Set openapi.docs.tryItOut.defaultServer to "first", "same-origin", "custom", or an exact server URL to choose the initial selection. openapi.docs.tryItOut.customServer defaults to true, so users can temporarily target another environment from the browser without changing project config.
Import external OpenAPI
The default Vext Docs page focuses on the OpenAPI document generated by the current application. To aggregate multiple external OpenAPI documents, use an external documentation platform or standalone UI outside Vext and point it at each service's /openapi.json. Vext does not expose a third-party docs renderer hook or install documentation UI packages.
Integrate with third-party tools
Export OpenAPI specification
Visit http://localhost:3000/openapi.json to get the complete OpenAPI 3.0 JSON file, which can be used for:
- Postman — import API collection
- Insomnia — Import API workspace
- Code Generation — Use
openapi-generatorto generate client SDK - API Gateway — Import to Kong, AWS API Gateway, and more
- Documentation Platform — Import into Stoplight, ReadMe, and more
Example: Generate TypeScript client
This optional external tool requires Java 11 or later. Its first invocation downloads an npm package and generator. Start the local API first and choose an output directory without handwritten code. See the official OpenAPI Generator installation guide for usage and prerequisites.
Documentation Best Practices
1. Always provide summary
summary is the most important identifier of the interface in the document list and should be concise and clear:
2. Use consistent tags
Global tags names should match inferred operation tags to add descriptions; they do not rewrite route tags or the built-in sidebar order. For example, /api/v1/** infers API v1 and /webhooks/** infers Webhooks:
3. Add documentation for error responses
Common error codes should be described in responses:
4. Hide internal interfaces
Interfaces used internally by the framework or for operation and maintenance should be marked as hidden:
5. Make good use of deprecated
When iterating the API version, use deprecated instead of directly deleting the old interface:
Multi-level directory example
VextJS's file routing supports multi-level nested directories, and each level of directory is automatically mapped to a URL path segment. The default Vext Docs page uses those OpenAPI path segments to build recursive API navigation, keeps stable resource-level segments as categories, and shows concrete operation leaves under that directory. Operation leaves prefer docs.summary; if no summary is configured, they fall back to the endpoint path. Dynamic path parameters such as {id} are treated as parameters rather than normal business directories. Operation tags are inferred automatically from the route path/source and shown as lightweight metadata badges; explicit x-tagGroups remains available only as vendor extension metadata.
Directory structure
Path mapping comparison
Global tags description
Predefine global tags only when you want to add descriptions for automatically inferred operation tags. Path segments remain the default navigation source:
Each routing file
The following directory layout belongs to a business application, not a second standalone Quick Start. Implement and load the user, order, dashboard, and payment services and allowlist and load the auth and check-role middleware first. Authentication and resource ownership checks are application responsibilities; directory grouping and docs declarations do not supply them. Without these dependencies, use the two-file example at the start of this page.
routes/api/v1/users.ts — User public interface
routes/api/v1/users/[id]/orders.ts — User orders (multi-level dynamic parameters)
routes/api/v1/admin/dashboard.ts — Management background
routes/api/v1/admin/users.ts — Management backend user management
routes/webhooks/stripe.ts — Third-party callbacks
Here payment.handleStripeWebhook(req, signature) is an application integration contract. An application plugin or adapter must provide restricted access to the raw request body; then use the Stripe SDK, signature, and endpoint secret to verify it before processing the event idempotently. Public VextRequest has no general raw-stream reader. Disabling the body parser does not produce raw content automatically, and req.body cannot substitute for it. This snippet shows directory and documentation declarations; it is not a ready-to-run payment integration. See Stripe's official signature guide for raw-body requirements.
Generated OpenAPI path
The above directory structure finally automatically generates the following OpenAPI paths. The default Vext Docs sidebar follows the path segments; tags remain operation metadata:
- Use directory hierarchy to express URL structure:
api/v1/admin/is automatically mapped to/api/v1/admin/prefix, no manual splicing is required - Dynamic parameters use the
[param]directory:users/[id]/orders.tsautomatically becomes/users/:id/orders, and theparamverification in the file will appear in the OpenAPI document - automatic operation tags: route-level
docs.tagsis deprecated and ignored; Vext infers operation tags from route path/source while the built-in docs page uses path segments for navigation - The file name is the route: No need for
app.group()or manual registration of routing prefixes, the directory structure is the routing structure :::
Tag groups (x-tagGroups)
The tags of the OpenAPI 3.x specification are one-dimensional flat lists and do not natively support nesting levels. When there are a large number of routes, all tags can become flat and hard to browse in a docs sidebar.
VextJS passes through x-tagGroups only when you explicitly configure openapi.tagGroups. The built-in Vext Docs renderer uses OpenAPI path segments as its primary recursive sidebar navigation, so x-tagGroups is treated as raw OpenAPI vendor extension metadata rather than a Vext Docs navigation feature.
Default behavior
VextJS does not generate x-tagGroups by default. The built-in Vext Docs renderer uses OpenAPI path segments as the source of truth for recursive navigation, so automatic tag grouping is unnecessary and can create misleading groups such as General / Admin.
Route-level docs.tags is deprecated and ignored. If another OpenAPI tool in your delivery chain needs x-tagGroups, explicitly specify tagGroups in the configuration and make sure the names match the automatically inferred operation tags or the global openapi.tags entries:
:::warning
tagGroups is emitted only when configured. Make sure every grouped tag name matches an operation tag or a global tags entry expected by the tool that consumes the OpenAPI document.
Effect comparison
Compatibility with hot reload
In dev mode, soft reload automatically regenerates the OpenAPI spec. If openapi.tagGroups is configured, the explicit x-tagGroups block is emitted again:
- Routing file changes → trigger hot reload
- Create a new adapter instance
- Reload routing + collect routing meta information
- Regenerate OpenAPI spec (including explicit
x-tagGroups, when configured) - Re-register the
/docsand/openapi.jsonendpoints on the new adapter
The new spec contains the configured extension after a route change; the built-in sidebar still does not reorder by tagGroups. Configuration changes follow the configuration restart flow; route soft reload is not configuration hot update.
Complete example
This is a complete order route file, not a standalone runnable project. First register auth as described in Security, and implement an order service: findAll(auth, filters) returns { items, total }; create(auth, data) and cancel(auth, id, reason) must enforce the current identity, ownership, and business state. Authentication establishes req.auth; declaring OpenAPI security alone does not verify credentials. Use the two-file example at the beginning of this page to verify documentation generation independently.
Built-in UI assets
The built-in renderer uses the same Vext mark geometry, teal/cyan light/dark theme tokens, green/amber mark accents, and favicon as the documentation website. These assets are bundled by Vext and remain consistent at custom docs paths; applications do not need to install a separate OpenAPI UI package.
Next step
- Understand how the DSL syntax of Parameter Validation maps to OpenAPI
- Learn the complete options of OpenAPI in Configuration
- See Adapter Architecture to understand the document behavior under different Adapters
- Discover how Testing verifies the accuracy of API documentation