Motivation
The Valibot website already lists "Config files" as one of the recommended use cases in the use cases guide:
Library authors can also make use of Valibot, for example, to match configuration files with a schema and, in the event of an error, provide clear indications of the cause and how to fix the problem.
However, there is currently no official tooling for this. Every project ends up writing the same small wrapper:
import * as v from 'valibot';
import { parse as parseYaml } from 'yaml';
import { readFileSync } from 'node:fs';
function loadAppConfig() {
const raw = readFileSync('app.config.yaml', 'utf8');
const parsed = parseYaml(raw);
return v.parse(AppConfigSchema, parsed);
}
Multiplied across formats (JSON / YAML / TOML / TS), discovery rules (multiple locations, env-specific overlays), and defaults merging, this becomes meaningful boilerplate.
Existing solutions:
cosmiconfig solves file discovery but leaves type inference entirely manual.
c12 solves it for the unjs ecosystem but pulls in unjs-specific dependencies (confbox, defu, etc.) and is not aware of Valibot.
Proposal
Add a new package @valibot/config-loader to this monorepo (alongside @valibot/to-json-schema and @valibot/i18n), exposing a single primary API:
import * as v from 'valibot';
import { loadConfig } from '@valibot/config-loader';
import { parse as parseYaml } from 'yaml';
const ConfigSchema = v.object({
port: v.pipe(v.number(), v.integer()),
database: v.object({ url: v.pipe(v.string(), v.url()) }),
});
const config = await loadConfig({
schema: ConfigSchema,
name: 'myapp',
envName: process.env.NODE_ENV,
parsers: { '.yaml': parseYaml, '.yml': parseYaml },
defaults: { port: 3000 },
});
// config is typed as v.InferOutput<typeof ConfigSchema>
Design principles
- Zero runtime dependencies in the core. JSON / JS / MJS / CJS are handled with Node built-ins (
JSON.parse, dynamic import()).
- Bring-your-own parser for YAML / TOML / INI / JSON5. Users pass
parsers: { '.yaml': fn }. This avoids coupling Valibot to any third-party parser library or external ecosystem.
- Automatic TypeScript inference from the schema — no manual
v.parse call.
- Pluggable merge strategy. Default is a shallow merge (zero deps). Users who want deep merge can pass
merge: defu or merge: lodash.merge.
jiti as an optional peerDependency for .ts config files (users who don't need it pay nothing).
- Mirrors the existing pattern of
@valibot/to-json-schema: only peerDependencies on valibot itself.
Scope (v1)
loadConfig (async only)
- File discovery (configurable cwd, multiple extensions)
- Environment overlays (
myapp.config.{envName}.{ext})
- Schema validation with full type inference
defaults with pluggable merge option (shallow by default)
- Built-in
.json / .js / .mjs / .cjs handling
- BYO parser API for everything else
Out of scope (v1)
- Sync API (deferred — ESM dynamic import is async by spec)
extends / layered config
- File watching
- Bundled YAML/TOML parsers (kept BYO to preserve zero-dep)
- Writing config files
Alternatives considered
cosmiconfig + valibot — works but every project rewrites the wrapper, and type inference is manual.
c12 + valibot — works but pulls in confbox, defu, and the broader unjs stack as hard dependencies.
- Website guide only — documenting the wrapper pattern does not remove the boilerplate.
- Community package — possible, but config loading is common enough to justify an official primitive like
@valibot/to-json-schema.
Naming
Proposed: @valibot/config-loader. Alternatives: @valibot/from-config, @valibot/load-config.
Prior art / context
- No existing issue, PR, or discussion proposes this.
- Discussion #1393 (CLI Builder) covers CLI argument parsing — a different domain.
- The use case is already officially endorsed in the use cases guide.
PoC
I've created a draft, and I'd like to discuss it based on that.
Motivation
The Valibot website already lists "Config files" as one of the recommended use cases in the use cases guide:
However, there is currently no official tooling for this. Every project ends up writing the same small wrapper:
Multiplied across formats (JSON / YAML / TOML / TS), discovery rules (multiple locations, env-specific overlays), and defaults merging, this becomes meaningful boilerplate.
Existing solutions:
cosmiconfigsolves file discovery but leaves type inference entirely manual.c12solves it for the unjs ecosystem but pulls in unjs-specific dependencies (confbox,defu, etc.) and is not aware of Valibot.Proposal
Add a new package
@valibot/config-loaderto this monorepo (alongside@valibot/to-json-schemaand@valibot/i18n), exposing a single primary API:Design principles
JSON.parse, dynamicimport()).parsers: { '.yaml': fn }. This avoids coupling Valibot to any third-party parser library or external ecosystem.v.parsecall.merge: defuormerge: lodash.merge.jitias an optionalpeerDependencyfor.tsconfig files (users who don't need it pay nothing).@valibot/to-json-schema: onlypeerDependenciesonvalibotitself.Scope (v1)
loadConfig(async only)myapp.config.{envName}.{ext})defaultswith pluggablemergeoption (shallow by default).json/.js/.mjs/.cjshandlingOut of scope (v1)
extends/ layered configAlternatives considered
cosmiconfig+valibot— works but every project rewrites the wrapper, and type inference is manual.c12+valibot— works but pulls inconfbox,defu, and the broader unjs stack as hard dependencies.@valibot/to-json-schema.Naming
Proposed:
@valibot/config-loader. Alternatives:@valibot/from-config,@valibot/load-config.Prior art / context
PoC
I've created a draft, and I'd like to discuss it based on that.