Skip to content

Proposal: Add a new package @valibot/config-loader #1473

@ysknsid25

Description

@ysknsid25

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

  1. Zero runtime dependencies in the core. JSON / JS / MJS / CJS are handled with Node built-ins (JSON.parse, dynamic import()).
  2. 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.
  3. Automatic TypeScript inference from the schema — no manual v.parse call.
  4. Pluggable merge strategy. Default is a shallow merge (zero deps). Users who want deep merge can pass merge: defu or merge: lodash.merge.
  5. jiti as an optional peerDependency for .ts config files (users who don't need it pay nothing).
  6. 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

  1. cosmiconfig + valibot — works but every project rewrites the wrapper, and type inference is manual.
  2. c12 + valibot — works but pulls in confbox, defu, and the broader unjs stack as hard dependencies.
  3. Website guide only — documenting the wrapper pattern does not remove the boilerplate.
  4. 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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions