Introduction
Typed, validated application config for TypeScript, built on TypeScript 7.1 content mappers.
tyfigo lets you import a config file as a module:
import config from '../config/app.config.yaml';
config.server.port; // number
config.database.url; // stringWhat you get back is the config for the current environment: app.config.yaml merged with app.production.config.yaml when
NODE_ENV=production, then with your local overrides, with ${PLACEHOLDERS} filled in from .env files and the real
environment. The object is deep-frozen.
The type comes from the file. A TypeScript 7.1 content mapper runs
inside tsc and the editor, so there is no codegen step and no .d.ts file to keep in sync. Environment and local files are
checked against the base file, and a wrong key or value is reported at its line in the YAML.
Two ways in
- Import the file.
import config from './app.config.yaml'needs no schema. The type is inferred from the base file and tsc checks every layer against it. - Add a schema.
loadConfig(config, schema)andloadEnv(schema)validate with a Standard Schema (Zod, Valibot, ArkType, …) or an Effect Schema, for the rules a file can't express: ranges, URLs, enums, required environment variables.
import { loadConfig, loadEnv } from 'tyfigo';
import { z } from 'zod';
import config from '../config/app.config.yaml';
const Config = z.object({
server: z.object({ host: z.string(), port: z.number().int().positive() }),
database: z.object({ url: z.url({ protocol: /^postgres$/ }) }),
});
const settings = loadConfig(config, Config);
const env = loadEnv(z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development') }));What it supports
- Formats: YAML, TOML, JSONC, JSON5, INI,
.propertiesand XML, for files named*.config.<ext>. See Formats. - Runtimes: Node 24+ (
--import tyfigo/register), Bun, nub, and Jest. - Bundlers: Vite and Vitest, Next.js (webpack and Turbopack), esbuild and tsup, Rollup, Rolldown and tsdown, webpack, Rspack and Rsbuild.
- Effect:
tyfigo/effecthas Effect versions of the loaders, aConfigServicehelper, layers and aConfigProvider.
tyfigo and Motherload
Motherload also imports data files through TypeScript 7.1 content mappers, and the two do different jobs.
| Motherload | tyfigo | |
|---|---|---|
| Purpose | A general data loader: import a data file and get its contents | Application config for the current environment |
| Files | Any .toml, .yaml, .jsonc, .json5, .csv, .tsv, .md, .txt, .env you list | *.config.{yaml,yml,toml,jsonc,json5,ini,properties,xml} and the files of a .config folder |
| Types | As as const would type the data (literal types) | Widened (number, string, readonly string[]), so other environments can change values |
| Layering | One file, one module | Base, environment and local files merged; overrides checked against the base |
.env | Importing a .env file sets its variables on process.env, typed as strings | .env files supply values to ${VAR} placeholders and loadEnv; process.env is never written |
| Validation | Parse and type errors in tsc; JSON Schema files through the separate dotschema package | Parse and type errors in tsc, plus Standard Schema or Effect Schema at run time |
| Effect | No | tyfigo/effect |
They can share a project. TypeScript gives each extension one owner and the longest suffix wins, so tyfigo can own
.config.yaml while Motherload owns .yaml:
{
"compilerOptions": { "skipLibCheck": true, "types": ["node"] },
"contentMappers": [
{ "package": "motherload", "extensions": [".yaml", ".toml"] },
{ "package": "tyfigo", "extensions": [".config.yaml", ".config.toml"] },
],
}Use Motherload for content (a list of team members, a CSV of prices, Markdown posts) and tyfigo for the settings your app reads at start-up.
The one layout that doesn't combine with Motherload is a .config directory: typing
.config/app.yaml means giving tyfigo .yaml itself, and an extension can only have one mapper.
Requirements
- TypeScript
7.1.0-betaor later, the first release with content mappers. - Node 24 or later (tyfigo uses
module.registerHooks), or Bun.