Validate Nuxt runtime config with Standard Schema at build time
npm install nuxt-safe-runtime-config
Validate Nuxt runtime config at build time using Zod, Valibot, ArkType, or any Standard Schema compatible library.
🔗 Related Nuxt RFC: Enable Standard Schema Validation in Nuxt Config
- 🔒 Build-time validation with Zod, Valibot, ArkType, or any Standard Schema library
- 🚀 Runtime validation (opt-in) validates config when the server starts
- ✨ Auto-generated types — useSafeRuntimeConfig() is fully typed without manual generics
- 🛠 ESLint plugin warns when using useRuntimeConfig() instead of the type-safe composable
- ⚡ Zero runtime overhead by default — validation happens at build time only
- 🔐 Shelve integration — fetch secrets from Shelve and merge into runtime config
Install the module:
``bash`
npx nuxi module add nuxt-safe-runtime-config
Use Zod, Valibot, ArkType, or any Standard Schema compatible library:
With Valibot
`typescript
import { number, object, optional, string } from 'valibot'
const runtimeConfigSchema = object({
public: object({
apiBase: string(),
appName: optional(string()),
}),
databaseUrl: string(),
secretKey: string(),
port: optional(number()),
})
`
With Zod
`typescript
import { z } from 'zod'
const runtimeConfigSchema = z.object({
public: z.object({
apiBase: z.string(),
appName: z.string().optional(),
}),
databaseUrl: z.string(),
secretKey: z.string(),
port: z.number().optional(),
})
`
With ArkType
`typescript
import { type } from 'arktype'
const runtimeConfigSchema = type({
'public': {
'apiBase': 'string',
'appName?': 'string'
},
'databaseUrl': 'string',
'secretKey': 'string',
'port?': 'number'
})
`
`typescript
export default defineNuxtConfig({
modules: ['nuxt-safe-runtime-config'],
runtimeConfig: {
databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/mydb',
secretKey: process.env.SECRET_KEY || 'default-secret-key',
port: Number.parseInt(process.env.PORT || '3000'),
public: {
apiBase: process.env.PUBLIC_API_BASE || 'https://api.example.com',
appName: 'My Nuxt App',
},
},
safeRuntimeConfig: {
$schema: runtimeConfigSchema,
},
})
`
Access your validated config with full type safety — types are auto-generated from your schema:
`vue`
| Option | Type | Default | Description |
| ------------------- | ------------------------------- | ----------------- | ------------------------------------------ |
| $schema | StandardSchemaV1 | — | Your validation schema (required) |validateAtBuild
| | boolean | true | Validate during dev/build |validateAtRuntime
| | boolean | false | Validate when server starts |onBuildError
| | 'throw' \| 'warn' \| 'ignore' | 'throw' | How to handle build validation errors |onRuntimeError
| | 'throw' \| 'warn' \| 'ignore' | 'throw' | How to handle runtime validation errors |logSuccess
| | boolean | true | Log successful validation |logFallback
| | boolean | true | Log when using JSON Schema fallback |jsonSchemaTarget
| | string | 'draft-2020-12' | JSON Schema version for runtime validation |shelve
| | boolean \| ShelveOptions | undefined | Shelve secrets integration (see below) |
Shelve is a secrets management service. This module fetches secrets from Shelve at build time and merges them into your runtime config before validation.
If you have a shelve.json file in your project root, the integration enables automatically:
`ts`
export default defineNuxtConfig({
safeRuntimeConfig: {
$schema: runtimeConfigSchema,
shelve: true, // Auto-detects project, team, and environment
},
})
The module resolves configuration from multiple sources (highest priority first):
| Config | Sources |
| ----------- | ---------------------------------------------------------------------- |
| project | nuxt.config → SHELVE_PROJECT → shelve.json → package.json name |nuxt.config
| slug | → SHELVE_TEAM_SLUG → shelve.json |nuxt.config
| environment | → SHELVE_ENV → shelve.json → dev mode auto |SHELVE_TOKEN
| token | → ~/.shelve file |
You can override any auto-detected value:
`ts`
export default defineNuxtConfig({
safeRuntimeConfig: {
$schema: runtimeConfigSchema,
shelve: {
project: 'my-app',
slug: 'my-team',
environment: 'production',
url: 'https://app.shelve.cloud', // Self-hosted Shelve
fetchAtBuild: true, // Default: fetch at build time
fetchAtRuntime: false, // Opt-in: fetch on server cold start
},
},
})
Shelve variables transform from SCREAMING_SNAKE_CASE to camelCase with smart grouping:
``
DATABASE_URL → databaseUrl
GITHUB_CLIENT_ID → github.clientId (grouped)
GITHUB_CLIENT_SECRET → github.clientSecret (grouped)
PUBLIC_API_URL → public.apiUrl
Variables with repeated prefixes (2+ keys) nest automatically. PUBLIC_ and NUXT_PUBLIC_ map to runtimeConfig.public.
For dynamic environments or secret rotation, enable runtime fetching:
`ts`
export default defineNuxtConfig({
safeRuntimeConfig: {
shelve: {
fetchAtBuild: true, // Bake secrets into build
fetchAtRuntime: true, // Also refresh on cold start
},
},
})
The runtime plugin runs before validation, so freshly fetched secrets are validated against your schema.
By default, validation only runs at build time. Enable runtime validation to catch environment variable issues when the server starts:
`ts`
export default defineNuxtConfig({
safeRuntimeConfig: {
$schema: runtimeConfigSchema,
validateAtRuntime: true,
},
})
Runtime validation uses @cfworker/json-schema to validate the config after environment variables are merged. This lightweight validator (~8KB) works on all runtimes including edge (Cloudflare Workers, Vercel Edge, Netlify Edge). It catches issues like:
- Environment variables with wrong types (e.g., NUXT_PORT=abc when expecting a number)
- Missing required environment variables in production
- Invalid values that pass build-time checks but fail at runtime
The module includes an ESLint plugin that warns when using useRuntimeConfig() instead of useSafeRuntimeConfig().
If you use @nuxt/eslint, the rule is auto-registered. No configuration needed.
Add to your eslint.config.mjs:
`javascript
import { configs } from 'nuxt-safe-runtime-config/eslint'
export default [
configs.recommended,
// ... your other configs
]
`
Or configure manually:
`javascript
import plugin from 'nuxt-safe-runtime-config/eslint'
export default [
{
plugins: { 'safe-runtime-config': plugin },
rules: { 'safe-runtime-config/prefer-safe-runtime-config': 'warn' },
},
]
`
The rule includes auto-fix support — run eslint --fix to automatically replace useRuntimeConfig() calls.
Types are auto-generated at build time from your schema's JSON Schema representation. The useSafeRuntimeConfig() composable returns a fully typed object — no manual generics needed:
`ts`
const config = useSafeRuntimeConfig()
// config is fully typed based on your schema
Generated types are stored in .nuxt/types/safe-runtime-config.d.ts and automatically included in your project.
When validation fails, you see detailed error messages:
``
[safe-runtime-config] Validation failed!
1. databaseUrl: Invalid type: Expected string but received undefined
2. public.apiBase: Invalid type: Expected string but received undefined
3. port: Invalid type: Expected number but received string
The module stops the build process until all validation errors are resolved.
Nuxt's built-in schema validation is designed for module authors and broader configuration. This module focuses specifically on runtime config validation using Standard Schema, allowing you to:
- Use your preferred validation library (Valibot, Zod, ArkType)
- Catch configuration errors at build time
- Optionally validate at runtime for environment variable issues
- Get full type safety in your components
Local development
`bashInstall dependencies
pnpm install