Configuration items
This page describes public configuration groups, defaults, override rules, and effective runtime boundaries. Read the Configuration guide first when configuring a project. Merge snippets into the appropriate existing file; do not combine mutually exclusive examples into multiple default exports. See each plugin's documentation for plugin-owned configuration. Internal _testMode and _runtimeMode are not user settings.
Framework constants, module-level fallback values, and explicit template configuration are different kinds of defaults. A field that parses but is not connected to runtime does not imply usable functionality.
Find configuration by task
For each field, check its default, unit, prerequisites, and route override support. Build identity records output information such as profile/buildId; a manifest is a generated inventory. These describe the output actually loaded, rather than additional business settings to configure.
Configuration loading mechanism
VextJS uses a multi-layer configuration merging strategy, in order of priority from low to high:
Plain objects are recursively merged. Arrays are generally replaced as a whole; middleware entries merge specially by name. After validation, plain objects and arrays are deeply frozen. Date, Map, Set, Buffer, and class instances retain runtime state and are not a hot-update channel for configuration.
The configuration profile is distinct from the runtime mode. Explicit selection follows --config → VEXT_CONFIG → nonstandard NODE_ENV (compatibility with a warning). Without an explicit selection, start/deploy assets first use the profile recorded in the selected successful build output, falling back to production only when no profile is recorded; build defaults to production and dev to development. start and build use production runtime mode; dev uses development mode. Even start --config development does not load local.ts, and a standard NODE_ENV does not replace --config for profile selection. See Configuration for commands.
For TypeScript, the layers do not share one loose type. Use VextUserConfig for the base default.ts; if it contains database, that nested value must be a complete MonSQLizeDatabaseConfig. Use VextConfigOverride for profile/local patches, where nested fields may be supplied incrementally according to the runtime deep merge.
Configuration file list
src/config/bootstrap.ts
When the database, key or configuration center patch needs to be injected before the configuration is frozen, you can add:
This illustrates the remote provider structure. Supply an accessible configuration service and a valid database patch, and validate the response JSON. A minimal local project can use providers: [] without contacting the example domain.
Constraints:
- provider must return plain object patch or
null - patches support JSON-like structures only, not functions or class instances; multiple providers merge in declaration order
timeoutMsis a hard deadline: expiry aborts the providersignal, and a patch returned by a late continuation is discarded rather than merged- When
requiredis not declared:productiondefaults to fail-fast,development/testdefaults to continue after warning - In Cluster mode, the same provider patch will be reused in the same startup cycle to prevent Master / Worker from seeing different results.
Configuration file example
Complete configuration reference
VextConfig
Configure automatic plugin deadlines through plugin; see VextPluginConfig.
host accepts "0.0.0.0", "::", an explicit IPv4 address, an explicit IPv6 address, or a hostname. With "::", the ready log prints IPv4 local URLs plus bracketed IPv6 local/network URLs such as http://[::1]:3000; explicit IPv6 hosts are printed with brackets too.
adapter
The following options are mutually exclusive: export one configuration in a real file. Implement a custom adapter instance in the application first. The underlying HTTP adapter supports three forms:
trustProxy
When set to true:
req.ipreads the first IP from theX-Forwarded-Forrequest headerreq.protocolis read from theX-Forwarded-Protorequest header
Enable this only behind a trusted proxy that overwrites forwarded headers. This switch does not have a trusted-proxy IP allowlist.
middlewares
Route-level middleware declarations. A string such as "auth" is equivalent to { name: "auth" }; full entries support options and enabled. Entries with the same name shallow-merge across layers, with options replaced as a whole. New names append, duplicate names in one layer fail, and enabled: false disables the entry. A referenced middleware must also exist and load from src/middlewares.
Global middleware (such as CORS, body-parser) is automatically registered by the framework and does not need to be declared here. Only routing-level optional middleware is declared here.
The first-party auth() helper is still registered as a route-level middleware file, then routes opt into protection with RouteOptions.auth:
VextCorsConfig
Cross-domain resource sharing configuration.
origins: ['*'] and credentials: true cannot be used at the same time. When you need to carry credentials, you must specify a specific domain name.
VextRateLimitConfig
Global rate limiting uses flex-rate-limit. By default this middleware runs before authentication, so the example limits by IP. See the Rate limiting guide for the full flow.
keyBy option
Once global limiting is explicitly enabled, a route can override max/window/string keyBy with options.override.rateLimit, or use false to skip it. A function keyBy must synchronously return a string; other strings are treated as IP. A route override alone does not enable global limiting.
Rate-limit store
rateLimit.store defaults to "memory"; it also accepts "redis" or a Redis options object. Process-local memory is not shared. Redis target selection is url → uri → VEXT_REDIS_URL → REDIS_URL; initialization fails if no target is found.
Instances sharing a policy should use the same target and prefix. Give distinct policies separate keys. Built-in store checks may allow a request on failure; see Storage failures and allow behavior.
VextPluginConfig
config.plugin controls automatic plugin loading and preserves custom application plugin fields.
vext dev, vext start, and createTestApp share this value. Timeout aborts the setup signal, stops later plugins, and invokes registered rollback cleanup; plugins must still honor cancellation and stop their work. The manual createTestApp({ setupPlugins }) callback is outside this loader deadline. Changes require restart; see Plugins.
VextLocaleConfig
Backend language configuration is separate from frontend.i18n:
Request metadata always fills req.locale independently of requestId. With request context enabled it also fills the store used by error translation; without context or outside a request, translation uses the app default. See Backend i18n for dictionaries and matching order, and Frontend i18n for inheritance or independent detection.
VextRequestIdConfig
Request IDs correlate requests and logs. A nonempty inbound header wins, then app.setRequestIdGenerator, config.generate, and randomUUID. IDs must be 1–512 characters without control characters. With enabled: false, the ID is empty and no response ID header is written; independent locale and propagated-header handling still runs.
requestId vs traceId
requestId is the unique identifier of the request built into vext, and traceId usually refers to the tracing ID generated by the APM link tracking system (such as OpenTelemetry / Jaeger). Both have different usage scenarios:
Mode 1: Custom correlation header
You may use x-trace-id as an application convention. Renaming the header does not create an OpenTelemetry trace/span or W3C Trace Context:
Mode 2: requestId and APM traceId coexist
Keep requestId. fetch.propagateHeaders can forward received tracing headers, but does not itself create an outbound span or new traceparent. For real tracing, initialize OpenTelemetry and verify an active context as shown in the OpenTelemetry example:
- Internal system, simple tracing → Mode 1 (rename header to
x-trace-id) - OpenTelemetry integration → Retain requestId and initialize, propagate, and verify trace/span using the plugin approach.
- See Request context and distributed tracing. :::
Generators can also be replaced dynamically via plugins:
VextFetchConfig
Built-in HTTP client and request proxy configuration.
timeout must be a finite positive number no greater than 2147483647 milliseconds. retryDelay must be a finite non-negative number no greater than 2147483647 milliseconds, and function return values are validated at runtime.
VextFetchProxyTargetConfig
Proxy request header priority: target.headers < forwardHeaders < target.defaultInjectHeaders < options.headers < options.injectHeaders. Authorization is not forwarded by default; both the allowlist and allowAuthorizationForward: true are required.
Proxy retry priority: options.retry > target.retry > config.fetch.retry > 0. Only GET / HEAD / OPTIONS / PUT / DELETE retry automatically on upstream 5xx or network errors. POST / PATCH do not retry by default. Timeouts do not retry and return local 504.
VextLoggerConfig
Structured log configuration, implemented by Vext's built-in logger kernel.
Log level priority (from high to low):
After setting a certain level, only logs of this level and higher will be output. Set to 'silent' to be completely silent.
The default logger also supports runtime app.logger.getLevel() / app.logger.setLevel(level) to adjust subsequent log thresholds; the configuration object itself will still be frozen after startup and should not be dynamically changed by modifying app.config.logger.level.
VextShutdownConfig
Graceful shutdown of configuration.
After receiving the SIGTERM / SIGINT signal, the framework will:
- Establish one absolute
timeoutdeadline when shutdown starts - Stop accepting new requests and wait for in-flight requests to complete
- Run
onClosein LIFO order, then close the response cache, lifecycle hooks, and logger - After the deadline, invoke cleanup that has not started without waiting further, then exit
VextServerConfig
Inbound Node.js HTTP server layer configuration. Applicable to built-in Native / Hono / Fastify / Express / Koa adapter, also applicable to development server created by vext dev. Unset fields retain the current Node.js default value.
config.server only controls inbound service requests. The timeout for outbound app.fetch / app.fetch.proxy is controlled by config.fetch.timeout, the proxy target timeout, or options when calling.
VextResponseConfig
Response format configuration.
Error logging fields
Logging still depends on logger.level. Schema validation errors are not automatically logged by http4xx: true. Logging policy and response hiding are independent.
Export packaging
When wrap: true is enabled, res.json(data) is automatically wrapped:
Error response format:
With wrap: false, res.json(data) sends raw data without changing the error response contract. rawJson, pages, text, and streams do not use the successful JSON wrapper.
Hide internal errors
hideInternalErrors only affects the "unknown exception" 500 error path, such as the scenario where throw new Error("...") is directly used in routing, service, and middleware. It does not change the status code and response format of structured errors such as app.throw(...) or VextValidationError.
When hideInternalErrors: true is used, 500 errors are not exposed stack trace:
VextBodyParserConfig
Request body parsing configuration.
After disabled, req.body is always undefined, which is suitable for pure GET service or custom body parsing scenarios.
maxBodySize supported formats:
VextMultipartConfig
Multipart/File upload global configuration.
:::tip Fastify linkage
multipart.maxFileSize only limits the size of a single file; the total request body read limit is controlled by bodyParser.maxBodySize. When using Fastify, if fastifyAdapter({ bodyLimit }) is additionally passed in, the actual read boundary will be the smaller value of the adapter bodyLimit and the overall upper limit of body-parser.
Storage and cleanup
Built-in multipart parsing is memory-only. Vext reads the request body and exposes each upload as req.files[*].buffer; it does not create a framework-managed temporary file or temporary directory. Consequently, there is no tmpDir, on-disk retention TTL, or periodic cleanup job to configure. When the request and application code no longer retain a buffer, normal Node.js garbage collection reclaims it; Vext never deletes files that your application stores itself.
Set bodyParser.maxBodySize, multipart.maxFileSize, multipart.maxFiles, and multipart.allowedMimeTypes deliberately. For large uploads, streaming object storage, or any durable file lifecycle, disable/avoid the built-in parser for that route and use a streaming upload plugin that owns its storage and cleanup policy.
VextAccessLogConfig
Access log configuration, implemented as onion-style after-middleware.
Access log output example:
Message fields include HTTP method, path, status code, response time (ms) and client IP; requestId is automatically injected into the JSON record field by logger's AsyncLocalStorage mixin.
VextOpenAPIConfig
OpenAPI documentation generation configuration.
docs.access.cacheKey is not a supported configuration field in this release. Vext rejects it to avoid implying response or access-result caching that the docs access pipeline does not currently provide.
For fixed local or deployed API targets, set servers[].url to the complete base URL including its port, for example http://127.0.0.1:3000. Use servers[].variables only for genuinely variable URL segments such as environment, region, tenant, or API version. docs.tryItOut.defaultServer controls the initial Try it out selection, while docs.tryItOut.customServer lets users temporarily enter another browser-side target without changing project config.
tagGroups is passed through as x-tagGroups only when explicitly configured. The default Vext Docs renderer builds recursive navigation from OpenAPI path segments; tagGroups is mainly for downstream OpenAPI tools that explicitly consume this vendor extension.
The default Vext Docs renderer derives Services / Utils / Models / Components / Plugins / Middlewares from code docs data. Model entries can show static schema fields, enums, options, indexes, methods, hooks, and usage. Plugins and middlewares can show inferred lifecycle/bootstrap, app extensions, middleware type, route usage, and source links. Locales, Config, Styles, and Preload are optional advanced static sources that can be enabled explicitly under docs.code; they are not shown in the default top-level documentation surface. Local loopback pages can also show Open source links for code docs entries without adding a separate configuration field.
guardSecurityMap legacy fallback
Automatically map routing middleware names to OpenAPI Security Scheme for legacy middleware-only routes. New Auth examples should declare the final RouteOptions.auth inline or in a same-file const so runtime protection, static projection, and OpenAPI security share the same source. Route-options helper calls are not supported by the finite static grammar:
securitySchemes
Supported security scheme types:
For apiKey schemes with in: "cookie" and for validate.cookie parameters, built-in docs can display the fields but browser Try it out cannot set the forbidden Cookie header directly. Use same-origin browser cookies or an HTTP client for manual cookie values.
VextRequestContextConfig
AsyncLocalStorage request context configuration.
:::warning After disabling, the following functions will be disabled:
- Logger automatically injects
requestId app.throw()automatically parses request-levellocaleapp.fetch()automatically propagatesrequestId:::
VextFrontendConfig
Built-in frontend build and static serving configuration.
Frontend i18n shares the effective locale across HTML, navigation envelopes, and freshness keys; detection merges Vary. inject:used component trimming remains reserved and diagnosed, while the app owns language switching. See Frontend i18n.
This configuration contract still has runtime limits: empty-shell hydration with ssr: false or clientOnly, production SSR CSS Module class consistency, direct server image imports, and static sitemap/robots MIME behavior. See Rendering modes, Styles and assets, Static assets, and SEO. Configurable fields do not resolve these limits.
The SPA scope example needs a real shell page. Start with scopes[].ssr: true and verify hydration and unknown-path behavior following CSR and SPA fallback.
Adapter extension contracts
The generic frontend.adapter resolver is deprecated. VextFrontendAdapter declares resolveBuildOptions(config), which is ignored; configuring a function emits a build diagnostic. Use the implemented build.client/build.server settings. The reserved field is planned for removal in the next breaking release; it does not enable another bundler, RSC, Server Functions, or PPR.
frontend.seo is documented end-to-end in SEO, Sitemap, and Robots. publicOrigin is a deployment origin, not a fixed page URL: the current pathname or an explicit page canonical supplies the per-page portion. Runtime artifacts accept only exact declared hosts; providers do not receive app or app.db implicitly.
For a delivery target other than the built-in local staging adapters, pass a VextFrontendDeployUploadAdapter object to deploy.upload.adapter. It provides name and upload(input). Its VextFrontendDeployUploadAdapterInput contains asset, sourcePath, uploadKey, and dryRun; its VextFrontendDeployUploadAdapterResult must return uploaded and may return url and etag.
filesystem and mock are the only built-in upload adapter names. A provider-specific adapter stays explicit in application configuration, so the runtime does not silently install or discover cloud/bundler plugins.
build.client.externalRuntime mappings also accept a URL string shorthand or an object with url, integrity, and crossOrigin.
By default spaFallback.scopes is empty, so unknown HTML paths are not swallowed into the SPA. For mixed SSR + client-router sub-apps, declare each basePath in scopes[]. spaFallback: true is kept only as a compatibility shorthand and is not recommended for enterprise mixed projects.
VextClusterConfig
Cluster multi-process configuration. For the complete interface definition, see src/types/app.ts VextClusterConfig.
Basic fields
healthCheck — heartbeat detection
reload — Zero-downtime rolling restart
Windows does not support the current reload signal operation. Long-lived connections may still break when an old Worker exits; see the Cluster guide.
cluster.reload only configures timing for rolling restarts triggered by vext reload / SIGHUP. Omitting cluster.reload does not disable rolling restart; Vext uses the defaults.
It can also be enabled through environment variables (no need to modify the configuration file):
VextCacheConfig
Route-level response cache global configuration.
Memory complete configuration:
Redis configuration:
MultiLevel configuration:
cacheHub only accepts response-cache-kit/cache-hub configuration and does not accept custom Store. Route-level response caching is configured via RouteOptions.cache. The public configuration unit is in milliseconds; the Cache-Control: max-age in the response header will output seconds according to the HTTP standard. See the Response Caching Guide for details.
VextDevConfig
Development-only configuration. These fields are read by vext dev and ignored in production.
VextDevOverlayConfig
VextDevMcpConfig
dev.mcp declares MCP intent for a project. It does not make the framework execute shell commands, start or restart services, run tests, or apply business files. The bundled vext mcp --root <dir> stdio server still returns analysis, machine-checkable input errors, and host-execution steps. vext mcp sync uses this declaration to write managed host configuration and returns read-back verification plus refresh guidance; Codex writes the user-level Codex config (CODEX_HOME/config.toml first, otherwise the current user's .codex/config.toml), and the AI host remains responsible for commands, business file application, and host restarts.
dev.mcp: true enables the default declaration. Use the object form when the project wants to document host targets or sync policy:
DEFAULT_CONFIG
This is a read-only snapshot of the DEFAULT_CONFIG constant, not a minimal project configuration. Module fallbacks may not appear explicitly in this constant, and actual values depend on merged layers:
VextUserConfig
Base-config input type for src/config/default.ts. Its top-level fields are optional because framework defaults supply omitted framework settings, but nested objects that you provide keep their own required fields. In particular, a database value must include a valid connection config; do not leave a half database for a later profile to complete. loadConfig() merges all layers into the complete VextConfig.
VextConfigOverride
Path-aware patch type for development.ts, production.ts, custom profiles,
local.ts, and createTestApp({ config }). Plain configuration objects follow
runtime deep-merge semantics, while arrays, functions, adapters, stores, and
registered runtime capabilities remain atomic.
A partial nested value is valid only when an earlier merged layer already owns
the required runtime data. For example, a profile may patch
database.findLimit after a complete base database exists; createTestApp()
does not load that project base, so adding database there requires a complete
database configuration.
Extending atomic paths
Application and plugin fields added through module augmentation are recursive
patches by default. If a custom path holds a client, class instance, adapter, or
another capability that must be supplied as a whole, add its root-relative dot
path to VextConfigOverrideAtomicPathRegistry in the same augmentation:
The registry is type-only and does not change runtime merge behavior. Register only genuine whole-value capabilities; ordinary configuration objects should remain recursively patchable.
VextSessionConfig
config.session.enabled: true auto-registers Session in production,
development, testing, and soft reload. The explicit session() middleware is
reserved for scoped/manual registration.
VextSessionCookieOptions follows CookieSerializeOptions and adds secure: boolean | "auto". Cookie options include domain, path, expires, maxAge, httpOnly, secure, sameSite, priority, partitioned, and encode.
VextSessionStore requires get(id), set(id, data, ttlSeconds), and delete(id). Optional methods are touch(id, ttlSeconds), clearExpired(), and close(). Vext calls close() during app shutdown for configured stores and active manual Session runtimes.
For cache-backed production sessions, prefer createCacheSessionStore(cacheLike, options?) from vextjs. It accepts a structural VextCacheLike with get, set, and del, converts session TTL seconds to cache milliseconds, stores JSON strings by default, and exposes close() only when options.close is provided. config.cache.cacheHub remains route response cache configuration and does not inject a Session Store. Session, RateLimit, Job, and response cache each own their Redis integration boundary; sharing one Redis server is fine, but each module should use its own generated namespace or explicit prefix.
RouteOptions.session accepts false, true, or { enabled?, rolling?, autoCommit? }. It can disable Session for one route or enable it while the global runtime is disabled.
VextCsrfConfig
config.csrf configures the built-in CSRF middleware. enabled: true auto-registers CSRF globally after body parsing and plugin global middleware. You can also keep it disabled and register csrf() manually for scoped paths.
Routes can opt out with route options { csrf: false }.
VextSecurityHeadersConfig
config.securityHeaders enables Vext's built-in browser security response headers. It is disabled by default. preset: "basic" is the low-impact path for most apps; strict and custom are explicit opt-ins.
basic sends X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, and X-Frame-Options: SAMEORIGIN. strict adds HTTPS-only HSTS, a minimal Permissions-Policy, COOP, and CORP, but still leaves CSP and COEP explicit. custom sends only fields you configure. Routes can opt out with { securityHeaders: false }.
loadConfig
Configuration loading function, receives the configuration directory path and performs the complete configuration chain merge.
Usually there is no need to call it manually, bootstrap() will automatically call loadConfig() internally. The merge order is: DEFAULT_CONFIG < default < config profile < local < bootstrap provider patch < CLI override.
Environment variable override
Some configurations support overriding through environment variables:
In PowerShell, set $env:VEXT_PORT and $env:VEXT_CONFIG before running npm start. The port must be an integer from 1 to 65535. The CLI rejects an invalid port; a direct invalid environment override may be ignored by a lower layer, so a successful start alone does not prove that the override applied.
Type declaration extension
Plug-ins can add custom fields to VextConfig through declare module:
Include this declaration file in tsconfig.include. Import vextjs so the declaration augments rather than replaces the module. Use satisfies VextUserConfig for configuration type checking:
Verify a configuration change
Run the existing type check and build, then restart the relevant process and check the actual port, feature entry point, and failure path. Configuration is frozen; editing a file does not update an already running instance. Choose practical checks from Rate limiting, Authentication and security, Session, Database, Frontend configuration, or Jobs API. When diagnosing, first confirm runtime mode/profile, working directory, provider success, and CLI overrides without dumping the whole configuration unnecessarily.