tyfigo

Introduction

Typed, validated application config for TypeScript, built on TypeScript 7.1 content mappers.

tyfigo lets you import a config file as a module:

src/main.ts
import config from '../config/app.config.yaml';

config.server.port; // number
config.database.url; // string

What 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

  1. 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.
  2. Add a schema. loadConfig(config, schema) and loadEnv(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.
src/main.ts
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, .properties and 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/effect has Effect versions of the loaders, a ConfigService helper, layers and a ConfigProvider.

tyfigo and Motherload

Motherload also imports data files through TypeScript 7.1 content mappers, and the two do different jobs.

Motherloadtyfigo
PurposeA general data loader: import a data file and get its contentsApplication config for the current environment
FilesAny .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
TypesAs as const would type the data (literal types)Widened (number, string, readonly string[]), so other environments can change values
LayeringOne file, one moduleBase, environment and local files merged; overrides checked against the base
.envImporting 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
ValidationParse and type errors in tsc; JSON Schema files through the separate dotschema packageParse and type errors in tsc, plus Standard Schema or Effect Schema at run time
EffectNotyfigo/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:

tsconfig.json
{
	"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-beta or later, the first release with content mappers.
  • Node 24 or later (tyfigo uses module.registerHooks), or Bun.

On this page