YAML to TypeScript Converter

Paste a deployment config, a CI file, or an API fixture and read back TypeScript interfaces you would be willing to commit. List items merge into one interface with optional keys, nulls become unions, keys with dashes get quoted, and every decision shows up in a notes list next to the output.

YAML to TypeScript workbench

YAML in0 lines
TypeScript outWaiting for YAML
0 types
Decisions the generator madenone yet

    What you get from one config file

    The sample loaded by the button above is a trimmed deployment file for toolexe.com. Three hosts, an owner block, a limits block, and a handful of top-level scalars. The output on the right is what the generator writes for the default settings, and the rest of this page explains each line of it.

    deploy.yml
    service: toolexe-web
    version: 3.14.0
    released: 2026-09-04
    replicas: 3
    debug: false
    owner:team: platform
    email: platform@toolexe.com
    on-call: Priya Raman
    hosts:- name: edge-fra-01
    region: eu-central
    port: 443
    tags: [cdn, tls]- name: edge-sin-02
    region: ap-southeast
    port: 8443
    canary: true
    - name: edge-iad-03
    region: us-east
    tags: []limits:cpu: 1.5
    memory: 512Mi
    timeout: ~
    deploy.ts
    export interface ToolexeConfig {service: string;version: string;released: string;replicas: number;debug: boolean;owner: Owner;hosts: Host[];limits: Limits;}
    export interface Owner {team: string;email: string;"on-call": string;}
    export interface Host {name: string;region: string;port?: number;tags?: string[];canary?: boolean;}
    export interface Limits {cpu: number;memory: string;timeout: null;}

    Four lines in the output are worth a second look. version is a string, not a number, because 3.14.0 has two dots and no YAML parser reads it as a float. released is a string by default even though the parser produced a Date, for reasons covered below. port and tags are optional because the third host has no port and the second has no tags. timeout is typed as null alone, since the file gives no other value to widen the type with, and the notes list flags it so you replace it by hand.

    How a value turns into a type

    The generator parses the YAML with js-yaml under the 1.2 core schema, walks the resulting tree, and applies the rules below in order. Reading them once tells you what to expect from any file.

    1. Scalars map by parsed type. Quoted or unquoted text is string. Integers, floats, hex, octal and .inf are number. true and false are boolean. YAML 1.1 spellings like yes and off are plain strings under the schema used here.
    2. Nulls widen the type.~, null and a bare key with no value all parse as null. The default setting appends | null to whatever other type the key carries. Switch to the optional setting and the key gets a ? instead, which suits config files where an unset value and a missing key mean the same thing.
    3. Mappings become named interfaces. Every nested mapping gets a declaration named after its key in PascalCase. owner becomes Owner, rate-limits becomes RateLimits. Two keys producing the same name get a numeric suffix rather than a silent merge.
    4. Lists become arrays of the merged item type. Item shapes are unioned. A list of mappings produces one interface named from the singular of the key, so hosts gives Host and policies gives Policy. Keys missing from some items are marked optional. A list mixing strings and numbers is (string | number)[]. An empty list is unknown[] until another item elsewhere tells the generator more.
    5. Keys are written as they are, with quotes when needed. A key with a dash, a dot, a space or a leading digit is not a valid identifier, so on-call is emitted as "on-call". Pick the camelCase setting and the key is renamed to onCall, with a note reminding you the runtime object still holds the original spelling.
    6. Multi-document files type the first document. A file split with --- is common for Kubernetes manifests, and each document usually has a different shape. The generator types the first one and says how many it skipped. Paste a single document to type a later one.

    Why list items merge instead of picking the first

    Most quick converters read the first item of a list and call it the type. On a file like the sample, the result is Host with a required port and no canary key at all, and the compiler then rejects the real object the moment you type it. Merging every item costs a little clarity in the output and pays for it with types the data satisfies.

    The merge treats keys and types separately. A key present in every item is required. A key present in some items is optional. A key whose values disagree in type across items becomes a union, so a timeout written as 30 on one host and 30s on another is number | string. The notes list reports each of these, because a union of that kind usually means a mistake in the YAML rather than a real design choice.

    Dates, timestamps and the string default

    js-yaml turns an unquoted 2026-09-04 into a JavaScript Date. Whether your code sees a Date depends entirely on which parser reads the file at runtime. A Node service using js-yaml gets a Date. A Go or Python service serving the same config over JSON sends a string. A YAML file loaded with the { schema: JSON_SCHEMA } option also gets a string. The generator defaults to string because the string case is the one where a wrong type produces a runtime error rather than a compiler complaint. Flip the dates setting to Date when you know the file is parsed by js-yaml with the default schema and never crosses a JSON boundary.

    Naming rules for generated declarations

    How keys become declaration names
    YAML keyDeclarationWhy
    owner:OwnerMapping, PascalCase of the key
    hosts:HostList of mappings, singular of the key
    policies:PolicyTrailing ies becomes y
    status:StatusWords ending in ss or us are left alone
    rate-limits:RateLimitsDashes and underscores split words
    2fa:Key2faLeading digit gets a prefix so the name compiles
    second owner: deeper in the treeOwner2Collisions get a suffix, never a merge

    Singularizing is a heuristic and English is not regular. data stays Data, series becomes Serie, and a key like news gives New. Rename in the output when this bites, or rename the key in the YAML if you own the file.

    Where the output falls short

    When a hand-written type is the better call

    Generated interfaces are a starting point for config files and API fixtures you did not write. They are a poor fit for a schema you already maintain. If a JSON Schema exists for the file, the YAML to JSON Schema converter followed by a schema-to-types tool gives you validation and types from one source. If the YAML is a fixture for a JSON API, convert with the YAML to JSON converter first and type the JSON with the JSON to TypeScript converter, so the sample you type matches what the wire carries, dates included. And if the file fails to parse at all, the YAML validator lists every problem at once instead of stopping at the first.

    Questions after the first conversion

    Optional keys nobody asked for, versions that turned into strings, and the other things people ask once the file is typed.

    Why is my version number typed as a string?

    A value like 3.14.0 has two dots, so no YAML parser reads it as a number and the generator sees a string. A single-dot value like 3.14 is a float and becomes number. If a version field should always be text, quote it in the YAML so a future 3.14 does not flip the type.

    A key is marked optional but every item in my list has it. Why?

    Check the spelling and casing across items. The merge treats port and Port as two different keys, each present in only some items, so both come out optional. Fixing the key in the YAML makes it required again. The notes list names the key and how many items carried it, which makes the odd one out easy to find.

    How do I get Record<string, T> instead of an interface with literal keys?

    The generator only emits fixed keys, because a mapping keyed by hostname and a mapping of real settings look identical in YAML. When every child of a mapping shares one shape, the notes list suggests a Record. Replace the emitted interface with Record<string, Child> by hand and keep the Child interface the generator wrote.

    Does the camelCase option rename the keys in my data?

    No. The option renames properties in the generated type only. The object your YAML parser produces still has on-call, not onCall, so the type will not match the data until you map the keys at load time. Use the option when a loader already camelCases keys, or when the type is a target for a mapping step you write yourself.

    Why does an empty list become unknown[]?

    An empty list carries no information about its items. If the same key holds a non-empty list somewhere else in a merged set of items, the two are combined and the empty one disappears from the result. If the list is empty everywhere, unknown[] is the honest type, and you should narrow it to what the code expects.

    What happens with a top-level list instead of a mapping?

    The root declaration becomes a type alias to an array, such as export type ToolexeConfig = Host[], with the item interface written after it. A top-level scalar produces a type alias to that scalar. The interface setting only affects mapping declarations, since an array or scalar cannot be an interface.

    Does the output compile under strict mode and noUncheckedIndexedAccess?

    Yes. The output uses only interfaces, type aliases, arrays, unions and the primitive types, with no any. Optional keys are written with the question mark syntax, not with undefined unions, so exactOptionalPropertyTypes also passes. Enable readonly if the config object is frozen after load.