Configuration items

This page details all configuration fields, types, default values and usage instructions of VextJS.

Configuration loading mechanism

VextJS uses a multi-layer configuration merging strategy, in order of priority from low to high:

DEFAULT_CONFIG (framework built-in default value)
  ↓ Deep merge
src/config/default.ts (project default configuration)
  ↓ Deep merge
src/config/{profile}.ts (config profile, such as production.ts or sg-sit.ts)
  ↓ Deep merge
src/config/local.ts (local override, optional)
  ↓ provider patch
src/config/bootstrap.ts (remote configuration during startup, optional)
  ↓ CLI override
vext start/dev --port --host ...

The merged configuration is deep-frozen through deepFreeze() and cannot be modified at runtime.

Configuration file list

FilePurposeIs it necessary
src/config/default.tsBasic configuration for all profiles
src/config/development.tsDefault development profile overridesOptional
src/config/production.tsDefault production profile overridesOptional
src/config/test.tsDefault test profile overridesOptional
src/config/sg-sit.tsCustom profile overridesOptional
src/config/local.tsLocal override (usually no Git commit)Optional
src/config/bootstrap.tsStartup provider registration entranceOptional

src/config/bootstrap.ts

When the database, key or configuration center patch needs to be injected before the configuration is frozen, you can add:

import { defineBootstrapConfig } from "vextjs";

export default defineBootstrapConfig({
  providers: [
    {
      name: "remote-config",
      timeoutMs: 10_000,
      async load({ configProfile, signal, baseConfig }) {
        const response = await fetch(
          `https://config.example.com/${configProfile}.json`,
          { signal },
        );
        const remote = await response.json();
        return {
          database: remote.database,
          logger: {
            lifecycleLevel: baseConfig.logger?.lifecycleLevel ?? "concise",
          },
        };
      },
    },
  ],
});

Constraints:

  • provider must return plain object patch or null
  • patch only supports JSON-like structure
  • When required is not declared: production defaults to fail-fast, development/test defaults 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

// src/config/default.ts
export default {
  port: 3000,
  adapter: "native",
  cors: {
    enabled: true,
    origins: ["http://localhost:3000"],
  },
  logger: {
    level: "debug",
  },
};
// src/config/production.ts
export default {
  port: 8080,
  cors: {
    origins: ["https://api.example.com"],
  },
  logger: {
    level: "warn",
  },
  response: {
    hideInternalErrors: true,
  },
};

Complete configuration reference

VextConfig

FieldTypeDefault ValueDescription
portnumber3000HTTP listening port
hoststring'0.0.0.0'HTTP listening address
adapterstring | Function | VextAdapter'native'Low-level adapter
trustProxybooleanfalseWhether to trust the proxy
middlewaresVextMiddlewareConfig[][]Route-level middleware whitelist
corsVextCorsConfigSee belowCORS configuration
rateLimitVextRateLimitConfigSee belowRate limit configuration
requestIdVextRequestIdConfigSee belowRequest ID configuration
loggerVextLoggerConfigSee belowLog configuration
shutdownVextShutdownConfigSee belowGraceful shutdown configuration
serverVextServerConfig{}Node.js HTTP server configuration
responseVextResponseConfigSee belowResponse configuration
sessionVextSessionConfigSee belowSession auto-registration, store, and cookie configuration
csrfVextCsrfConfigSee belowCSRF middleware configuration
securityHeadersVextSecurityHeadersConfig{ enabled: false }Browser security response headers
bodyParserVextBodyParserConfigSee belowBody parsing configuration
multipartVextMultipartConfigundefinedFile upload configuration
accessLogVextAccessLogConfigSee belowAccess log configuration
openapiVextOpenAPIConfigSee belowOpenAPI documentation configuration
requestContextVextRequestContextConfigSee belowRequest context configuration
fetchVextFetchConfigSee belowBuilt-in HTTP client and proxy configuration
frontendboolean | VextFrontendConfig{ enabled: false }Built-in frontend build and static serving configuration
clusterPartial<VextClusterConfig>undefinedCluster multi-process configuration

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 underlying HTTP adapter supports three parameter passing methods:

// Method 1: String identification (built-in adapter)
export default {
  adapter: "native", // 'native' | 'hono' | 'fastify' | 'express' | 'koa'
};

// Method 2: Factory function (pass in custom options)
import { fastifyAdapter } from "vextjs/adapters/fastify";

export default {
  adapter: fastifyAdapter({ bodyLimit: 5 * 1024 * 1024 }),
};

//Method 3: Custom adapter instance (implementing VextAdapter interface)
export default {
  adapter: myCustomAdapter,
};

trustProxy

When set to true:

  • req.ip reads the first IP from the X-Forwarded-For request header
  • req.protocol is read from the X-Forwarded-Proto request header

This option needs to be enabled when deployed behind Nginx/cloud load balancer.

middlewares

Route-level middleware whitelist declaration. Only middleware declared here can be referenced in routes options.middlewares.

export default {
  middlewares: [
    { name: "auth" },
    { name: "framework-auth" },
    { name: "admin", options: { role: "admin" } },
    { name: "client-cache", options: { maxAge: 60 } },
  ],
};
Tip

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:

// src/middlewares/framework-auth.ts
import { auth, defineMiddleware } from "vextjs";

export default defineMiddleware(
  auth({
    async verify(token) {
      return token === "demo-token" ? { userId: "1" } : false;
    },
  }),
);

VextCorsConfig

Cross-domain resource sharing configuration.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable CORS
originsstring[]['*']Allowed origin domain names
methodsstring[]['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']Allowed HTTP methods
headersstring[]['Content-Type', 'Authorization', 'X-Request-Id']Allowed request headers
credentialsbooleanfalseWhether to allow carrying credentials
maxAgenumberundefinedCORS preflight result cache time (seconds)
export default {
  cors: {
    enabled: true,
    origins: ["https://app.example.com", "https://admin.example.com"],
    credentials: true,
    maxAge: 86400,
  },
};
Warning

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 limit configuration, implemented based on flex-rate-limit.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable rate limiting
maxnumber100Maximum number of requests within the time window
windownumber60Time window (seconds)
messagestring'Too Many Requests'Overrun error message
keyBystring | Function'ip'Request source identifier
export default {
  rateLimit: {
    max: 200,
    window: 120,
    //Limit flow by user ID (requires auth middleware to parse the user first)
    keyBy: (req) => req.user?.id ?? req.ip,
  },
};

keyBy option

valuedescription
'ip'Limit flow by client IP (default)
'user'Press req.user?.id to limit current
(req) => stringCustom function, returns unique identifier
Tip

The routing level can override the global configuration via options.override.rateLimit, or set it to false to disable rate limiting.


VextRequestIdConfig

Request ID tracing configuration for log correlation and distributed link tracing.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable request ID
headerstring'x-request-id'From which request header to read (gateway transparent transmission)
responseHeaderstring'x-request-id'The name to write the response header
generate() => stringcrypto.randomUUID()Custom ID generation function

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: requestId acts as traceId (simple scenario)

Change the request header name of requestId to x-trace-id to unify it with the link tracking header, which is suitable for systems that do not rely on external APM:

import { nanoid } from "nanoid";

export default {
  requestId: {
    header: "x-trace-id", // Read from x-trace-id (gateway injection)
    responseHeader: "x-trace-id", // Write back the response header
    generate: () => nanoid(), // Can be replaced by a shorter ID generator
  },
};

Mode 2: requestId + APM traceId coexist (enterprise-level scenario)

Keep requestId (log association), and transparently transmit APM's traceparent header through config.fetch.propagateHeaders, suitable for connecting to OpenTelemetry / Jaeger and other systems:

export default {
  // requestId retains the default configuration (for log correlation)
  requestId: {
    header: "x-request-id",
    responseHeader: "x-request-id",
  },
  // APM tracking headers are automatically transparently transmitted to downstream services through propagateHeaders
  fetch: {
    propagateHeaders: ["traceparent", "tracestate"],
  },
};
Select suggestions
  • Internal system, simple tracing → Mode 1 (rename header to x-trace-id)
  • Access OpenTelemetry / Jaeger / Datadog → Mode 2 (retain requestId, configure propagateHeaders)
  • For details, see [Request context → Relationship with distributed tracing](/guide/request-context#Relationship with distributed tracing traceId) :::

Generators can also be replaced dynamically via plugins:

app.setRequestIdGenerator(() => myCustomId());

VextFetchConfig

Built-in HTTP client and request proxy configuration.

FieldTypeDefault ValueDescription
timeoutnumber10000app.fetch and app.fetch.proxy default timeouts
retrynumber0The default number of retries, indicating the number of additional attempts
retryDelaynumber | (attempt: number) => number1000Default retry interval, supports function form
propagateHeadersstring[][]Common app.fetch request header whitelist for automatic transparent transmission
proxyVextFetchProxyTargetConfig[][]List of upstream targets for app.fetch.proxy.<name>()

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.

export default {
  fetch: {
    timeout: 10_000,
    retry: 1,
    retryDelay: 500,
    propagateHeaders: ["traceparent", "x-tenant-id"],
    proxy: [
      {
        name: "userService",
        baseURL: "http://user-service:3001/api",
        forwardHeaders: ["x-tenant-id"],
        headers: { "x-source": "gateway" },
        timeout: 5000,
        retry: 1,
      },
    ],
  },
};

VextFetchProxyTargetConfig

FieldTypeRequiredDescription
namestringTarget name, corresponding to app.fetch.proxy.<name>(); reserved name then cannot be used
baseURLstringUpstream base URL
headersRecord<string, string>Target-level fixed request headers
forwardHeadersstring[]Whitelist of request headers transparently transmitted from the current req.headers
defaultInjectHeadersRecord<string, string> | FunctionTarget-level dynamic injection headers
allowAuthorizationForwardbooleanWhether to allow transparent transmission of the original Authorization
timeoutnumberTarget-level timeout
retrynumberNumber of target-level retries
retryDelaynumber | (attempt: number) => numberTarget-level retry interval

Proxy request header priority: target.headers < forwardHeaders < target.defaultInjectHeaders < options.headers < options.injectHeaders. Authorization does not transmit transparently by default, and both whitelist and allowAuthorizationForward: true must be configured.Agent retry priority: options.retry > target.retry > config.fetch.retry > 0. Only GET / HEAD / OPTIONS / PUT / DELETE will automatically retry when upstream 5xx or network error occurs; POST / PATCH does not retry by default, does not retry when timeout and returns local 504.


VextLoggerConfig

| Structured log configuration, implemented based on Vext’s built-in logger kernel. | Field | Type | Default Value | Description | | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------- | -------- | -------------- | ------------------------------ | | level | 'fatal' \| 'error' \| 'warn' \| 'info' \| 'debug' \| 'trace' \| 'silent' | 'info' | log level | | lifecycleLevel | 'concise' \| 'verbose' | 'concise' | Framework life cycle log detail level, control system log output such as startup, loader, hot reload, cluster, etc. | | pretty | boolean | Development environment true | Whether to use the built-in pretty formatter to output readable format | | prettyIgnore | string | 'pid,hostname,requestId' | Fields to ignore in pretty mode (comma separated). Hiding requestId by default prevents mixin-injected fields from being expanded into multi-line noise, and the production environment JSON output is not affected | | prettySingleLine | boolean | true | Whether to compress extra fields in the same line of the message as JSON inline in pretty mode. Set to false to use multi-line expansion format. Only affects pretty mode, production environment JSON output is not affected | | redactKeys | string[] | [] | Desensitize structured log fields by exact key at any level. The top level level is the log protocol field and will not be overwritten | | redactPaths | string[] | [] | Desensitize structured log fields by dot notation exact path, support array numeric subscript; do not support wildcard, bracket notation, remove or function censor | | redactValue | string | '[Redacted]'' | Desensitized replacement value | | mixin |() => Record<string, unknown> |undefined | Customized log mixin function, the return value will be merged with the framework's built-in fields and injected into each log.requestIdis a framework protected field and cannot be overridden by user mixin; other fields such astrace_id/span_idare given priority by user mixin. Typical use: Inject OpenTelemetrytrace_id/span_id` to associate logs with link tracking. User mixin calls will not be executed when not configured. |

export default {
  logger: {
    level: "debug",
    pretty: true, // Development environment beautification output
    // prettySingleLine: true, // Default value, extra fields are compressed to the same line of the message
    // prettySingleLine: false, // Restore multi-line expansion format
    // prettyIgnore: 'pid,hostname,requestId', //Default value, hide requestId
    // prettyIgnore: 'pid,hostname', // To display requestId in pretty mode
    // redactKeys: ['password', 'token'],
    // redactPaths: ['user.email', 'headers.authorization'],
    // redactValue: '[Redacted]',
  },
};

Log level priority (from high to low):

fatal > error > warn > info > debug > trace

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.

FieldTypeDefault ValueDescription
timeoutnumber10Shutdown timeout (seconds)

After receiving the SIGTERM / SIGINT signal, the framework will:

  1. Stop accepting new requests
  2. Wait for the in-flight request to complete (no more than timeout seconds)
  3. Execute all onClose hooks in LIFO order
  4. Exit the process
export default {
  shutdown: {
    timeout: 30, // Container environment recommends 30 seconds
  },
};

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.

FieldTypeDefault ValueDescription
requestTimeoutnumberNode.js default valueMaximum time in milliseconds to receive a complete request, 0 means disabled
headersTimeoutnumberNode.js default valueMaximum time to receive complete HTTP headers (milliseconds)
keepAliveTimeoutnumberNode.js default valueKeep-alive idle wait time after response completes (milliseconds)
socketTimeoutnumberNode.js default valuesocket inactivity timeout (milliseconds), 0 means disabled
maxHeaderSizenumberNode.js default valueMaximum request header size (bytes)
maxRequestsPerSocketnumberNode.js default valueThe maximum number of requests for a single socket, 0 means unlimited
connectionsCheckingIntervalnumberNode.js default valueOutstanding request timeout check interval (milliseconds)
export default {
  server: {
    requestTimeout: 120_000,
    headersTimeout: 60_000,
    keepAliveTimeout: 5_000,
    socketTimeout: 0,
    maxHeaderSize: 16 * 1024,
    maxRequestsPerSocket: 0,
    connectionsCheckingInterval: 30_000,
  },
};

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.FieldTypeDefault ValueDescription
hideInternalErrorsbooleantrueWhether to hide 500 error details
wrapbooleantrueWhether to enable export packaging

Export packaging

When wrap: true is enabled, res.json(data) is automatically wrapped:

{
  "code": 0,
  "data": { "id": 1, "name": "Alice" },
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Error response format:

{
  "code": 10001,
  "message": "User does not exist",
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

When wrap: false is disabled, res.json(data) sends raw data directly.

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:

//hideInternalErrors: true
{ "code": 500, "message": "Internal Server Error" }

// hideInternalErrors: false (for development environment only)
{ "code": 500, "message": "Internal Server Error", "stack": "..." }

VextBodyParserConfig

Request body parsing configuration.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable body parsing
maxBodySizestring | number'1mb'Maximum request body size
export default {
  bodyParser: {
    maxBodySize: "5mb", // Supports 'kb', 'mb', 'gb' units
  },
};

After disabled, req.body is always undefined, which is suitable for pure GET service or custom body parsing scenarios.

maxBodySize supported formats:

FormatExampleDescription
String'1mb', '512kb', `'10mb''Support kb/mb/gb unit
Number1048576Directly specify the number of bytes

VextMultipartConfig

Multipart/File upload global configuration.

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable built-in multipart parsing. After setting to true, body-parser will automatically fill in req.files without plug-in
maxFileSizenumber10485760Maximum size of a single file (bytes, default 10MB)
maxFilesnumber10Maximum number of files in a single request
allowedMimeTypesstring[]undefinedWhitelist of allowed MIME types (if not set, there will be no restriction)
export default {
  multipart: {
    enabled: true, // Enable built-in parsing
    maxFileSize: 10 * 1024 * 1024, // 10MB
    maxFiles: 5,
    allowedMimeTypes: [
      "image/jpeg",
      "image/png",
      "image/gif",
      "application/pdf",
    ],
  },
};

:::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.


VextAccessLogConfig

Access log configuration, implemented based on onion model after-middleware.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable access logs
levelstring'info'Basic log level, only supports 'info' or 'debug'
skipPathsstring[][]Exact match skipped path list
skipPathPrefixesstring[][]List of paths skipped by prefix matching
slowThresholdnumber0Slow request threshold, 0 means not enabled
warnOn4xxbooleanfalseWhether to promote 4xx responses to warn
logResponseSizebooleanfalseWhether to append the response body size at the end of the message
export default {
  accessLog: {
    enabled: true,
    level: "info",
    skipPaths: ["/health", "/readiness", "/metrics"],
    skipPathPrefixes: ["/internal"],
    slowThreshold: 1000,
    warnOn4xx: false,
    logResponseSize: false,
  },
};

Access log output example:

POST /api/users 201 12ms | 192.168.1.1

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.

FieldTypeDefault ValueDescription
enabledbooleandev enabled, prod disabledWhether to enable OpenAPI generation
titlestringundefinedDocument title
versionstringundefinedDocument version
descriptionstringundefinedDocument description
docs.pathstring'/docs'Vext Docs path
docs.assetsPathstring'/_vext/docs'Vext Docs built-in asset and data endpoint prefix, including app.js / style.css / favicon.svg and source-aware JSON data
docs.assetsPublicPathstringSame as docs.assetsPathBrowser-facing docs asset/data prefix for prefix-stripping reverse proxies; HTML uses this public prefix for app.js / style.css / favicon.svg
docs.renderer'vext''vext'Built-in Vext Docs renderer. Third-party renderer objects are no longer supported; external tools should consume /openapi.json
docs.ui.theme'system' | 'light' | 'dark''system'Built-in Vext Docs color theme. Visitors can override it locally in the UI
docs.ui.density'comfortable' | 'compact''comfortable'Built-in Vext Docs spacing density. Visitors can override it locally in the UI
docs.code.enabledboolean | 'auto''auto'Whether to generate code docs from services / utils / models / components / plugins / middlewares and explicitly enabled optional static sources
docs.code.scan'lazy' | 'background''lazy'Code docs scan lifecycle. lazy scans on each docs data request; background warms one in-process snapshot at docs registration and reuses it for later requests
docs.code.componentsboolean | objecttrueComponent JSDoc source. Defaults to src/frontend/components/**; only discovered entries appear in the UI
docs.code.pluginsboolean | objecttruePlugin JSDoc/runtime source. Defaults to src/plugins/**; only discovered entries appear in the UI
docs.code.middlewaresboolean | objecttrueMiddleware JSDoc/runtime source. Defaults to src/middlewares/**; only discovered entries appear in the UI
docs.code.localesboolean | objectfalseOptional locale source. When enabled, scans src/locales/** and src/frontend/locales/**; set dir to scan one custom locale root
docs.code.configboolean | objectfalseOptional runtime config source. When enabled, scans src/config/**
docs.code.stylesboolean | objectfalseOptional frontend style source. When enabled, scans src/frontend/styles/**
docs.code.preloadboolean | objectfalseOptional project preload source. When enabled, scans project-root preload/**
docs.access.mode'off' | 'visibility-only' | 'enforce''off'Docs UI data, menu and operation access mode
docs.access.openapiJson'filtered' | 'public''filtered'Whether canonical /openapi.json is filtered by docs access rules or remains public
docs.sourcesArray[]Optional source surfaces for multi API / multi version docs. Every source requires match; non-All code docs need explicit code.include / code.exclude
docs.tryItOut.hookScriptstringundefinedOptional browser script loaded for Try it out request / response hooks
docs.tryItOut.hookGlobalstring'VextDocsHooks'Browser global lookup name for Try it out beforeRequest / afterResponse hooks
docs.tryItOut.defaultServerstringundefinedInitial Try it out server: "first", "same-origin", "custom", or an exact OpenAPI server URL
docs.tryItOut.sameOriginboolean | 'auto''auto'Whether to show the Same origin Try it out server option. auto shows it only when no OpenAPI servers are configured
docs.tryItOut.customServerbooleantrueWhether visitors can temporarily enter a custom Try it out base URL in the browser
docs.tryItOut.customServerUrlstringundefinedOptional default value for the Custom server input
docsPathstring'/docs'Compatibility field; prefer docs.path in new projects
jsonPathstring'/openapi.json'OpenAPI JSON path
jsonPublicPathstringSame as jsonPathPublic canonical spec path for links and external tools. Built-in source-aware docs data uses docs.assetsPublicPath / docs.assetsPath; see the guide
contactobjectundefinedContact information
licenseobjectundefinedLicense information
serversarrayundefinedServer address list
tagsarrayundefinedGlobal tag definition
tagGroupsArray<{ name: string; tags: string[] }>undefinedExplicit OpenAPI x-tagGroups vendor extension output; the built-in Vext Docs default navigation does not depend on it
guardSecurityMapRecord<string, string>undefinedGuard to Security Scheme mapping
securitySchemesobjectundefinedSecurity scheme definition
scalarobjectundefinedDeprecated compatibility field. It only triggers a warning when explicitly configured and does not affect the built-in Vext Docs page
tryItOutEnabledbooleantrueDeprecated Compatibility only; it does not affect the default Vext Docs implementation
docExpansion'none' | 'list' | 'full''list'Deprecated Compatibility only; it does not affect the default Vext Docs implementation

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.

export default {
  openapi: {
    enabled: true,
    title: "My API",
    version: "1.0.0",
    description: "My API Documentation",
    docs: {
      path: "/docs",
      renderer: "vext",
      code: {
        enabled: "auto",
      },
    },
    servers: [
      { url: "http://localhost:3000", description: "Development environment" },
      { url: "https://api.example.com", description: "Production environment" },
    ],
    tags: [
      { name: "User", description: "User management interface" },
      { name: "Order", description: "Order Management Interface" },
    ],
    securitySchemes: {
      bearerAuth: {
        type: "http",
        scheme: "bearer",
        bearerFormat: "JWT",
      },
    },
    guardSecurityMap: {
      auth: "bearerAuth",
    },
  },
};

guardSecurityMap legacy fallback

Automatically map routing middleware names to OpenAPI Security Scheme for legacy middleware-only routes. New Auth examples should prefer RouteOptions.auth through a local route guard helper so runtime protection and OpenAPI security share the same source:

// Legacy only: middleware-name inference without RouteOptions.auth
app.get("/profile", { middlewares: ["auth"] }, handler);
// ↑ OpenAPI automatically infers that this route requires bearerAuth authentication

securitySchemes

Supported security scheme types:

typeDescriptionRequired fields
httpHTTP authenticationscheme (bearer / basic)
apiKeyAPI Keyname, in (header / query / cookie)
oauth2OAuth 2.0
openIdConnectOpenID Connect

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.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable the request context
export default {
  requestContext: {
    enabled: false, // Disabled to increase RPS by 3-8%
  },
};

:::warning After disabling, the following functions will be disabled:

  • Logger automatically injects requestId
  • app.throw() automatically parses request-level locale
  • app.fetch() automatically propagates requestId :::

VextFrontendConfig

Built-in frontend build and static serving configuration.

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable frontend integration
frameworkstring'react'Frontend framework label
rootstring'src/frontend'Frontend source directory
pages.dirstring'pages'Page directory resolved from root
pages.extensionsstring[]['.tsx', '.jsx', '.ts', '.js']Extensions scanned for pages, layouts, error pages, and locale modules
pages.documentstring'pages/_document.html'Document template path resolved from root
pages.errorDirstring'pages/error'Error page directory resolved from root
componentsDirstring'components'Shared component directory resolved from root
styles.entrystring'styles/index.css'Global CSS entry resolved from root
styles.jscssboolean | object{ enabled: true }Vext JSCSS build-time CSS extraction and dynamic CSS variables
styles.jscss.filesstring[]['**/*.style.ts', '**/*.style.js', '**/*.css.ts']JSCSS source file glob patterns
styles.jscss.runtimeAdapter'css-variables' | 'none' | false'css-variables'Emits dynamic variables through CSS custom properties, or falls back to static fallback values
styles.jscss.dynamicVarsbooleantrueWhether to emit JSCSS custom property declarations and var(...) references
styles.jscss.recipesbooleantrueWhether recipe variants emit additional classes and CSS rules
assetsDirstring'assets'Bundled frontend asset directory resolved from root
entrystring'.vext/generated/frontend/browser-entry.tsx'Generated browser entry; usually not written by hand
indexHtmlstring'src/frontend/pages/_document.html'HTML document template
outDirstring.vext/client in dev, dist/client in productionFrontend output directory
publicDirstring'public'Static assets copied into the frontend output
publicPathstring'/'Public asset path prefix
aliasobjectBuilt-in @frontend/@pages/@components/@styles/@assetsFrontend-safe aliases; no default alias points at all of src
spaFallbackboolean | object{ scopes: [] }Serve fallback only for explicitly declared client-router sub-app scopes
spaFallback.enabledbooleantrueEnables scoped fallback arbitration; with no scopes, no path is captured
spaFallback.excludestring[]['/api/**', '/openapi.json', '/docs/**', '/_vext/docs/**']Global fallback exclusion paths
spaFallback.scopes[]object[][]Explicit client-router sub-app scopes
apiClientboolean | objecttrueGenerate client contract artifacts
apiClient.enabledbooleantrueWhether to emit client-contract.json and api.generated.ts
render.ssrbooleantrueEnable SSR rendering
render.fallback'client' | 'error''client'Whether SSR failures fall back to a client shell or an error response
render.timeoutMsnumber3000Timeout for one SSR render
render.layoutbooleantrueWhether to enable the nested layout chain
errorPages.defaultstring'error/default'Default error page id
errorPages.statusobject{ 404: 'error/404', 500: 'error/500' }Status code to error page id mapping
i18nobject{ enabled: false }Frontend page message layer, SSR messages, and {vext.lang}
i18n.sourcestring'locales'Frontend message directory resolved from root
i18n.defaultLocale'inherit' | string'inherit'Default locale; inherit follows the request-level locale
i18n.detectstring[]['accept-language']SSR locale detection sources
i18n.inject'used' | 'all''used'Whether to inject used messages or all messages
i18n.clientSwitch'reload''reload'Initial client locale switch strategy
i18n.clientLoad'current' | 'all''current'Whether the browser loads only the current SSR locale or all locales
i18n.htmlLangbooleantrueWhether to write {vext.lang} / <html lang>
i18n.varybooleantrueWhether locale affects response vary/cache behavior
dev.hotbooleantrueDevelopment frontend hot update channel
dev.fastRefreshbooleantrueReact Fast Refresh
dev.transport'sse''sse'Transport for the Vext dev event bus
dev.overlaybooleantrueWhether to show frontend dev browser overlays for rebuild errors and render refresh prompts
dev.debounceMsnumber50File change event debounce interval
dev.renderRefresh'prompt' | 'auto' | 'off''prompt'Browser behavior after render-related route/service backend changes
build.targetstring | string[]'es2022'Browser build target
build.minifybooleanProduction trueMinify frontend output
build.sourcemapbooleanDevelopment trueGenerate frontend source maps
build.clientobjectInherits shared build defaultsBrowser bundle output, hash names, splitting, and external entries
build.client.assetsDirstring"assets"Browser bundle asset subdirectory under frontend.outDir
build.client.splittingbooleantrueBrowser code splitting
build.client.externalstring[][]Modules externalized from the browser bundle
build.client.externalRuntimeobject{}Import map URL mapping for externalized browser modules; React externals fail without mappings
build.serverobjectserver/renderer.cjsSSR renderer bundle output
build.server.outFilestringserver/renderer.cjsSSR renderer file under frontend.outDir
build.vendorChunksboolean | object{ enabled: true }Vext-managed vendor entry and shared chunk handling
build.budgetsobjectAll 0Frontend asset budgets; 0 disables a budget
build.budgets.maxInitialJsGzipBytesnumber0Initial JS gzip budget
build.budgets.maxInitialJsBrotliBytesnumber0Initial JS brotli budget
build.budgets.maxRouteInitialJsBrotliBytesnumber0Per-route initial JS brotli budget
build.budgets.maxAppOwnedInitialJsBrotliBytesnumber0App-owned initial JS brotli budget excluding external runtime assets
build.assets.inlineLimitnumber0Imported asset inline limit; default emits hashed files
build.css.modulesbooleantrueWhether to support the CSS Modules convention
build.diagnostics.metafilebooleantrueWhether to keep internal esbuild metafile diagnostics for size report / leak scan
build.diagnostics.sizeReportbooleantrueWhether to emit a size report
build.diagnostics.performanceReportbooleantrueWhether to keep route-level initial JS metrics in build reports
build.diagnostics.leakScanbooleantrueBlocks browser bundles from importing server-only modules
deploy.assetBaseUrlstringNoneAbsolute CDN prefix for static frontend assets
deploy.crossOrigin'anonymous' | 'use-credentials'Nonecrossorigin value injected into script/link tags
deploy.integritybooleanfalseInject build-time SRI into generated JS/CSS tags
deploy.uploadboolean | object{ enabled: false, exclude: ["**/*.map"] }Static asset upload config; vext deploy assets uploads incrementally by sha256
export default {
  frontend: {
    enabled: true,
    framework: "react",
    publicDir: "public",
    publicPath: "/",
    spaFallback: {
      scopes: [
        {
          basePath: "/admin/app",
          page: "admin/app/shell",
          exclude: ["/admin/api/**"],
        },
      ],
    },
  },
};

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

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable Cluster mode (can also be enabled by VEXT_CLUSTER=1)
workers'auto' | 'auto-1' | number'auto'Number of Workers ('auto' = detected available CPU count; 'auto-1' = available CPU count - 1; number = fixed number, clamped to [1, 64])
autoRestartbooleantrueWorker automatically restarts after crash
maxRestartsnumber5The maximum number of restarts allowed within the fast restart detection window
restartWindownumber60000Fast restart detection window (milliseconds)
restartBaseDelaynumber1000Restart interval backoff base (milliseconds)
restartMaxDelaynumber30000Upper limit of restart interval (milliseconds)
pidFilestring'.vext.pid'PID file path (for vext stop / vext reload to locate the process)
titlePrefixstring'vext'Worker process title prefix
sticky'none' | 'ip''none'Sticky session mode ('ip' allocates fixed Worker based on client IP, suitable for WebSocket/SSE)

healthCheck — heartbeat detection

FieldTypeDefault ValueDescription
healthCheck.enabledbooleantrueWhether to enable Worker heartbeat detection
healthCheck.intervalnumber15000The interval at which the Master sends heartbeat detections (milliseconds)
healthCheck.timeoutnumber30000Worker heartbeat timeout threshold (milliseconds), forced restart after timeout

reload — Zero-downtime rolling restart

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.

FieldTypeDefault ValueDescription
reload.workerDelaynumber2000Time to wait before replacing the next Worker (milliseconds)
reload.readyTimeoutnumber30000New Worker readiness timeout (milliseconds)
reload.shutdownTimeoutnumber10000Old Worker shutdown timeout (milliseconds)
export default {
  cluster: {
    enabled: true,
    workers: "auto", // Detect available CPUs via availableParallelism / cgroup v1 / os.cpus
    autoRestart: true,
    maxRestarts: 5,
    healthCheck: {
      enabled: true,
      interval: 15000,
      timeout: 30000,
    },
    reload: {
      workerDelay: 2000,
      readyTimeout: 30000,
      shutdownTimeout: 10000,
    },
  },
};

It can also be enabled through environment variables (no need to modify the configuration file):

VEXT_CLUSTER=1 vext start

VextCacheConfig

Route-level response cache global configuration.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable route-level response caching. When set to false, the cache middleware will not be installed and the Redis/MultiLevel connection will not be opened
defaultTtlnumber60000The default value when the route does not specify a TTL, in milliseconds
maxEntriesnumber1000Memory mode quick configuration: maximum number of cache entries
maxMemorynumberMemory mode quick configuration: maximum memory usage bytes
cleanupIntervalnumber0Memory mode quick configuration: periodic cleaning interval, 0 means only lazy cleaning
cacheHubobjectMemoryUnderlying response cache runtime configuration
export default {
  cache: {
    enabled: true,
    defaultTtl: 120_000,
    maxEntries: 2000,
  },
};

Memory complete configuration:

export default {
  cache: {
    defaultTtl: 60_000,
    cacheHub: {
      mode: "memory",
      maxEntries: 1000,
      maxMemory: 50 * 1024 * 1024,
      cleanupInterval: 30_000,
      enableStats: true,
    },
  },
};

Redis configuration:

export default {
  cache: {
    defaultTtl: 2_000,
    cacheHub: {
      mode: "redis",
      url: "redis://localhost:6379",
      deleteCommand: "unlink",
      lease: {
        waitForOwner: 1_000,
        onTimeout: "fetch",
      },
      distributed: {
        channel: "vext:response-cache",
      },
    },
  },
};

MultiLevel configuration:

export default {
  cache: {
    defaultTtl: 60_000,
    cacheHub: {
      mode: "multi-level",
      memory: { maxEntries: 1000 },
      redis: { url: "redis://localhost:6379" },
      writePolicy: "both",
      backfillOnRemoteHit: true,
      remoteTimeout: 50,
      lease: true,
    },
  },
};

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.


DEFAULT_CONFIG

The full value of the framework’s built-in default configuration:

import { DEFAULT_CONFIG } from 'vextjs';

// Complete content of DEFAULT_CONFIG:
{
  port: 3000,
  host: '0.0.0.0',
  adapter: 'native',
  trustProxy: false,
  middlewares: [],
  cors: {
    enabled: true,
    origins: ['*'],
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
    headers: ['Content-Type', 'Authorization', 'X-Request-Id'],
    credentials: false,
  },
  rateLimit: {
    enabled: true,
    max: 100,
    window: 60,
    message: 'Too Many Requests',
    keyBy: 'ip',
  },
  requestId: {
    enabled: true,
    header: 'x-request-id',
    responseHeader: 'x-request-id',
  },
  logger: {
    level: 'info',
  },
  shutdown: {
    timeout: 10,
  },
  server: {},
  response: {
    hideInternalErrors: true,
    wrap: true,
  },
  session: {
    enabled: false,
    name: 'vext.sid',
    ttl: 86400,
    rolling: false,
    autoCommit: true,
    idLength: 32,
    cookie: {
      httpOnly: true,
      sameSite: 'lax',
      path: '/',
      secure: 'auto',
    },
  },
  csrf: {
    enabled: false,
    mode: 'auto',
    methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
    headerNames: ['x-csrf-token', 'x-xsrf-token'],
    bodyField: '_csrf',
    cookie: {
      name: 'vext.csrf',
      httpOnly: false,
      sameSite: 'lax',
      path: '/',
      secure: 'auto',
    },
    fetchMetadata: true,
    origin: false,
  },
  securityHeaders: {
    enabled: false,
    preset: 'basic',
  },
  bodyParser: {
    enabled: true,
    maxBodySize: '1mb',
  },
  accessLog: {
    enabled: true,
    level: 'info',
    skipPaths: [],
  },
  openapi: {
    enabled: false,
  },
  requestContext: {
    enabled: true,
  },
  frontend: {
    enabled: false,
  },
}

VextUserConfig

User-configured input type, all fields are optional. The complete VextConfig is generated by loadConfig() merging the default values.

import type { VextUserConfig } from "vextjs";

const config: VextUserConfig = {
  port: 8080,
  logger: { level: "debug" },
};

export default config;

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.

FieldTypeDefaultDescription
enabledbooleanfalseAuto-register Session globally
namestring'vext.sid'Session cookie name
ttlnumber86400Store TTL in seconds
rollingbooleanfalseRefresh the store TTL and cookie on each request
autoCommitbooleantruePersist dirty session data before response send
idLengthnumber32Random byte length for the CSPRNG session id; must be 16-128
cookieVextSessionCookieOptionsSee belowSession cookie attributes
storeVextSessionStorememory storeCustom async store for shared deployments

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.

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.

FieldTypeDefaultDescription
enabledbooleanfalse in app config; true for manual csrf()Whether global auto-registration is enabled
mode"auto" | "session" | "signed-cookie""auto"Token storage mode
secretstringundefinedRequired for signed-cookie mode
methodsstring[]["POST", "PUT", "PATCH", "DELETE"]Unsafe methods that require CSRF validation
headerNamesstring[]["x-csrf-token", "x-xsrf-token"]Header names accepted for submitted tokens
bodyFieldstring | false"_csrf"Request body field accepted for submitted tokens; false disables it
cookieCookieSerializeOptions{ name: "vext.csrf", sameSite: "lax", path: "/" }Signed double-submit cookie attributes
fetchMetadatabooleantrueReject Sec-Fetch-Site: cross-site unsafe requests
originfalse | { trustedOrigins?: string[] }falseOptional Origin/Referer same-origin enforcement

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.

FieldTypeDefaultDescription
enabledbooleanfalseAuto-register the built-in Security Headers middleware
preset"basic" | "strict" | "custom""basic"Header preset
contentTypeOptions"nosniff" | falsepreset controlledControls X-Content-Type-Options
referrerPolicystring | falsepreset controlledControls Referrer-Policy
frameOptions"DENY" | "SAMEORIGIN" | falsepreset controlledControls X-Frame-Options
hstsfalse | { enabled?: boolean; maxAge?: number; includeSubDomains?: boolean; preload?: boolean; force?: boolean }false in basicControls Strict-Transport-Security; sent only for HTTPS unless force: true
contentSecurityPolicyfalse | string | objectfalseControls CSP or CSP report-only
permissionsPolicyfalse | string | Record<string, boolean | string[]>false in basicControls Permissions-Policy
crossOriginOpenerPolicyfalse | "same-origin" | "same-origin-allow-popups" | "unsafe-none"false in basicControls COOP
crossOriginEmbedderPolicyfalse | "require-corp" | "credentialless" | "unsafe-none"falseControls COEP; not enabled by strict
crossOriginResourcePolicyfalse | "same-origin" | "same-site" | "cross-origin"false in basicControls CORP
headersRecord<string, string>{}Custom headers merged last
skipPathsstring[][]Exact paths or trailing-* prefixes to skip
export default {
  securityHeaders: {
    enabled: true,
    preset: "basic",
    contentSecurityPolicy: {
      reportOnly: true,
      directives: {
        "default-src": ["'self'"],
        "upgrade-insecure-requests": true,
      },
    },
  },
};

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.

import { loadConfig } from "vextjs";
import { join } from "node:path";

const config = await loadConfig(join(process.cwd(), "src/config"), {
  rootDir: process.cwd(),
  command: "start",
  isBuilt: false,
});
// config: VextConfig (merged, frozen)

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:

Environment variablesCorresponding configurationDescription
VEXT_PORTportStrict whole-value port override from CLI/runtime transport
VEXT_HOSThostHost override from CLI/runtime transport
VEXT_CONFIGSelect the config profile to load
NODE_ENVRuntime mode; vext start runs as production
VEXT_CLUSTERcluster.enabledSet to 1 to enable clustering
VEXT_PORT=8080 VEXT_CONFIG=sg-sit vext start

Type declaration extension

Plug-ins can add custom fields to VextConfig through declare module:

// types/vext.d.ts
declare module "vextjs" {
  interface VextConfig {
    redis?: {
      host: string;
      port: number;
      password?: string;
    };
  }
}

Later use in the configuration file will get full type hints:

// src/config/default.ts
export default {
  redis: {
    host: "localhost",
    port: 6379,
  },
};