T tomlkit·org
Inspect Formatter Validator Convert TOMLJSON JSONTOML TOMLYAML YAMLTOML INITOML TOMLINI .envTOML TOML.env TOMLTS Transform Sort keys Flatten Minify Compare Diff Merge

TOML to TypeScript Types

updated 8 June 2026

Emit a set of TypeScript interface declarations matching the shape of a TOML file. Use it to type-check a loaded config without writing the types by hand.

What this tool does

It parses a TOML document into a value tree, then walks that tree and writes TypeScript that describes its shape. Every type comes from a real value, not a schema you supply: a string becomes string, a number becomes number, a table becomes its own nested interface named in PascalCase, and an array of tables becomes Name[] typed from its first element. You choose whether the output is interface declarations, a typed value literal, or both.

The intent it closes: "I load a config.toml in a TypeScript project and I'm tired of typing it as unknown." Paste the config, pick a mode, and drop the result into a .d.ts or alongside your loader — you get autocomplete and compile-time checking that matches the file you actually ship.

One thing to know up front: this infers from one sample, so every key becomes a required field. There is no optionality, no union of "this key is sometimes absent" — the generated types describe exactly the document you pasted.

When you'd reach for it

  • Type a static config without hand-writing it. Leave Output on Interface, paste config.toml, and copy the export interface Config block into a .d.ts.
  • Inline the config as a typed constant. Switch Output to Const literal to get export const config = {…} — the values baked into source, no runtime file read.
  • Ship both the type and a default value. Pick Interface + typed const so the const is annotated with the interface (export const config: Config = {…}) and the two stay in lockstep.
  • Freeze a config object. Set Readonly to Yes to prefix every property with readonly and append as const to the literal, so the shape can't be mutated.
  • Keep datetimes as real Date values. Switch Dates to Date when your loader rehydrates timestamps instead of leaving them as ISO strings.
  • Sketch a starting point for a feature-flag or theme file. Generate from a representative sample, then hand-edit the keys you know are optional.

How the conversion works

One click of Generate runs three stages.

1. Parse the TOML

The full TOML 1.0 grammar is parsed into a value tree. The root must be a table — a bare value or fragment is rejected before anything is emitted. Dotted keys collapse into nested objects, arrays-of-tables ([[…]]) become arrays of objects, and datetimes become date-shaped values that the next stage recognizes.

2. Infer a type for each value

The walk decides a TypeScript type per key from the value alone. Scalars use JavaScript's own typeof (string, number, boolean); a datetime maps to your Dates choice; null becomes the literal type null. Arrays are inspected element by element — an empty array is unknown[], a homogeneous one is T[], and a mixed one is (T1 | T2 | …)[]. The full mapping is in the table below.

3. Emit, recursing into nested tables

Each table is written as its own export interface, named by PascalCasing the key it sits under. A nested table is referenced by name and emitted afterwards; an array of tables is typed as Name[] using its first element as the template. If two tables would claim the same name, the second is suffixed (Database, then Database2) so every interface stays unique. The chosen mode then assembles the final output — interfaces, a value literal, or both.

Options reference

Output

Three modes. Interface (types only) emits just the export interface declarations — the shape, no data. Const literal (values only) emits export const config = {…} with the actual values baked in and no interface. Interface + typed const emits both and annotates the const with the root interface, so export const config: Config = {…} is checked against its own type. Note the const identifier is always config; the Root name field controls the interface name, not the variable.

Root name

The name of the top-level interface, default Config. Nested tables ignore this and take their names from their keys; only the root uses it. In Interface + typed const mode this is also the type used to annotate the const.

Readonly

No emits ordinary mutable properties. Yes prefixes every interface property with readonly and, in any mode that emits a const, appends as const to the literal. Use it when the config is meant to be immutable after load — the compiler then rejects accidental reassignment.

Dates

TOML has real datetime types; TypeScript needs to know how your loader surfaces them. string (ISO) types a datetime as string (and writes the literal as a quoted ISO string), which suits loaders that leave timestamps as text. Date types it as Date (and writes the literal as new Date("…")) for loaders that rehydrate real date objects.

Output (the generated types)

How each TOML value is typed:

TOML valueInferred TypeScript
Stringstring
Integer or floatnumber
Booleanboolean
Datetime (Dates = string)string
Datetime (Dates = Date)Date
A null-valued keynull
Empty arrayunknown[]
Homogeneous arrayT[]
Mixed array(T1 | T2 | …)[]
Tablenested interface, PascalCase name
Array of tablesName[], typed from the first element

Other facts about the output:

  • Every key is required. No ? is ever emitted — the types match the one sample you pasted.
  • Keys that aren't valid identifiers are quoted. A key like "build-id" is written as "build-id": rather than a bare property.
  • Interface names are deduplicated. Collisions get a numeric suffix (Server, Server2).
  • Download: config.d.ts.

Example

Input (TOML):

app_name = "MyApp"
debug = false

[database]
host = "localhost"
port = 5432

[[users]]
name = "Alice"
admin = true

[[users]]
name = "Bob"
admin = false

Output (Interface mode, default root name, dates as string):

export interface Config {
  app_name: string;
  debug: boolean;
  database: Database;
  users: Users[];
}

export interface Database {
  host: string;
  port: number;
}

export interface Users {
  name: string;
  admin: boolean;
}

The [database] table becomes its own Database interface; the two [[users]] blocks become Users[], typed from the first block. Bob's entry is never inspected — if the two users had different shapes, only the first would shape the type.

Recipes by intent

Drop types into a .d.ts

Leave Output on Interface, set Root name to whatever fits (e.g. AppConfig), generate, and download config.d.ts. Reference the root interface where you call your TOML loader: const cfg = load() as AppConfig;.

Inline a config as a typed constant

Switch Output to Interface + typed const. You get the interfaces plus export const config: Config = {…} with the values inlined — no runtime file read, and the literal is checked against its own interface.

Make the config immutable

Set Readonly to Yes. Interface properties gain readonly and, in const or both mode, the literal ends with as const, so TypeScript treats the whole object as frozen and narrows literal types.

Carry datetimes as real Date objects

Set Dates to Date. Datetime keys are typed Date and, in a const literal, written as new Date("…") — matching a loader that rehydrates timestamps instead of leaving them as strings.

Limits and performance

  • In-memory. The input, the parsed tree, and the generated source all sit in memory together. Real config files are instant; this isn't a tool for multi-megabyte data dumps.
  • One sample defines the type. Arrays of tables are typed from the first element only, and every key is required. If your real data varies, the generated types will be narrower than reality.
  • The textarea is the slow part on large output. If you generate a const literal from a big config, rendering it into the right pane can lag; prefer Download .d.ts over Copy.
  • Best for stable shapes. Static configs, theme files, and feature-flag schemas type cleanly; dynamic data whose shape shifts at runtime does not.

Errors and how to fix them

"TOML root must be a table."

The input parsed to something that isn't a top-level table — typically a bare value or a fragment. TOML files are tables at the root, so wrap your data in keys (or a [section]) and re-run.

A parse error with a line and column

The input isn't valid TOML 1.0. Common causes: an unquoted string value, a missing =, an unclosed array or quote, or a duplicate key. The TOML Validator points at the exact spot before you generate types.

A property came out as unknown[]

That key held an empty array, so there's no element to infer a type from. Paste a sample with at least one representative item in the array, then hand-edit if you need a more specific element type.

A field is a union like (string | number)[]

That array mixed element types. TOML allows heterogeneous arrays, so the inferred type widens to a union of every type seen. That's correct for the sample — narrow it by hand if your real data is actually homogeneous.

An interface is named Foo2 instead of Foo

Two tables in your document resolved to the same PascalCase name, so the second was suffixed to keep names unique. Rename the clashing keys in your TOML if you want cleaner generated names.

FAQ

Does it handle optional keys?

No. Inference runs on a single sample, so every key becomes a required field — no ? is emitted. Mark fields optional by hand in the output where a key can be absent.

How are arrays of tables typed?

As Name[], with the interface generated from the first element only. If later elements have extra or differently typed keys, those aren't reflected — pass the most complete element first, or merge a representative shape before generating.

Is my data uploaded?

Never. Parsing and code generation run entirely in your browser. You can disconnect from the network after the page loads and it still works — safe for configs that contain secrets or hostnames.

Should I pick string or Date for datetimes?

Match your loader. If it returns timestamps as ISO strings, use string; if it rehydrates them into JavaScript Date objects, use Date so the type and a const literal (new Date("…")) line up with the runtime value.

Can it generate a Zod or Valibot schema instead?

Not yet — but the structure is the same shape, so it's a plausible future addition. Send me a request at [email protected] if you'd use it.