CSV to YAML Converter

Turn a spreadsheet of rows into a YAML list you can commit. Each row becomes one mapping, each column gets a type decided once, and anything YAML would misread comes back quoted. The file never leaves your machine.

Columns to rename or drop before you generate the file? Open the app

Config that starts life in a spreadsheet

A table is the right place to decide a list of settings and the wrong place to ship them. Forty rows of thresholds are easy to review side by side in a sheet and miserable as forty indented blocks. So people fill in the sheet, then generate the file:

  • Per-service deployment settings. Replica counts, log levels and regions for twenty services across three environments. Sixty rows, one file, one pull request.
  • Feature flags and rollout percentages. Product decides these in a spreadsheet. The application reads YAML.
  • Ansible variables and inventory data. Host lists and per-machine overrides are naturally tabular and conventionally stored as YAML.
  • Monitoring and alert definitions. Metric names, thresholds, and who gets paged. As a table you notice the one row with no owner.
  • Seed data and fixtures. Roles, permissions, plan tiers, country lists. Test runners in Ruby, Python and Go commonly read YAML.

Worked example: a deployment matrix

Two services across two environments, the sort of sheet filled in during planning that then has to become a real file:

service,environment,replicas,log_level,autoscale,zone
billing-api,production,6,info,true,eu-west-1
billing-api,staging,2,debug,false,eu-west-1
web-frontend,production,4,warn,true,us-east-1
web-frontend,staging,1,debug,false,us-east-1

The YAML, exactly as it comes out:

- service: billing-api
  environment: production
  replicas: 6
  log_level: info
  autoscale: true
  zone: eu-west-1
- service: billing-api
  environment: staging
  replicas: 2
  log_level: debug
  autoscale: false
  zone: eu-west-1
- service: web-frontend
  environment: production
  replicas: 4
  log_level: warn
  autoscale: true
  zone: us-east-1
- service: web-frontend
  environment: staging
  replicas: 1
  log_level: debug
  autoscale: false
  zone: us-east-1

Column order is kept, so the keys read in the order you arranged them. replicas came through as numbers and autoscale as real booleans, because every value in those columns qualified. The widget reports the row count underneath, which is the cheapest check that nothing was dropped.

A type per column, not a type per cell

CSV has no types. Something has to decide whether 6 is a number or the text "6", and most converters decide cell by cell. That produces a column of mixed numbers and strings, which loads fine and then throws a type error three services downstream.

Here the decision is made once per column, and only if every value round trips. A value qualifies as a number when writing that number back gives the identical text. So a price column holding 9.99 and 12.50 stays text top to bottom, because 12.50 would come back as 12.5. Change that cell to 12.5 and the column becomes numbers. Booleans work the same way: lowercase true and false qualify, while a column of Y and N stays text.

An empty cell becomes null, not an empty string and not zero. In config that distinction usually decides whether a default applies.

The quoting that saves you in six months

YAML resolves bare words into types, and older readers resolve more of them than you expect. A country list is the classic casualty: Norway's code is no, and unquoted that is the boolean false. A sheet built from exactly those hazards:

country_code,ships,version,zip
no,no,1.10,01730
se,yes,2.0,02139

And the output, with quotes exactly where they are needed:

- country_code: 'no'
  ships: 'no'
  version: '1.10'
  zip: '01730'
- country_code: se
  ships: 'yes'
  version: '2.0'
  zip: '02139'

Note that se is bare while no is quoted, in the same column. Quoting is per value, based on what that specific text would resolve to, so the file stays readable instead of turning into JSON with different punctuation. Single letters catch people out too: y comes out as 'y' while x stays bare, because only one is a boolean to somebody's parser.

Gotchas worth knowing

  • The output is always a flat list of mappings. If you need a top-level key wrapping the list, or grouping by a column, add that by hand. No option guesses at it.
  • Dotted headers do not become nested keys. A column named resources.cpu produces a key spelled resources.cpu.
  • A header row with no data under it is refused, with a message rather than an empty list, which is valid YAML and a confusing thing to be handed.
  • Duplicate headers are renamed, not dropped. A second name column becomes name_2, with a note.
  • The delimiter is sniffed, not configured. Comma, semicolon, tab and pipe all work without a setting.
  • Commas inside quoted cells are safe. Files up to 100 MB, no row cap, no daily quota.

Frequently Asked Questions

What shape is the YAML I get back?

A sequence of mappings, one per data row, with the header cells as keys. Fifty rows give fifty entries in a single list. That is the shape most config loaders expect, and the shape our YAML to CSV tool reads back into the same rows.

Can a column named server.host create a nested key?

No. A header of server.host becomes a key spelled server.host, dot included, not a server mapping with a host inside it. Building nesting from punctuation is guesswork, and it mangles any column whose name legitimately contains a dot.

Why did my whole price column come out as text?

Types are decided once per column rather than per cell. A column holding 9.99 and 12.50 stays text, because 12.50 would print back as 12.5. A column holding 9.99 and 12.5 becomes numbers. The alternative is a column half numbers and half strings.

Why is a cell that said no wrapped in quotes?

Because unquoted, no is the boolean false in YAML 1.1 readers, which is how Norway's country code disappears from a generated config. Any value some reader might resolve as a boolean, a number or null is quoted, so 'no', 'yes', 'y', '01730' and '1.10' survive as text.

Does it read semicolon files, and what about duplicate headers?

Comma, semicolon, tab and pipe are sniffed from the text, so a semicolon export needs no setting and spreadsheet cells paste in as tab separated text. Duplicate headers are renamed rather than dropped: a second name column becomes name_2, with a note.

Is anything uploaded, and what if my file has no data rows?

Nothing is uploaded. The conversion runs in your tab, with nothing kept between visits, up to 100 MB per file with no daily quota. A header row with nothing under it is refused with a message, rather than an empty list that looks like success.

Convert your CSV to YAML

No sign-up, no upload, no row cap. One mapping per row, columns typed once, risky values quoted for you.

Back to the converter