The tomlet logomark: a folded golden omelette dotted with diced paprika

TOML + omelette · a tidy little parser for Gleam

TOML that
keeps your
comments.

tomlet parses TOML 1.1 into a document that remembers every comment, key order, and bit of whitespace. Edit a value, write it back, and the rest of the file comes out exactly how the human left it.

gleam add tomlet
See it round-trip ↓
  • TOML 1.1 by default,strict 1.0 on request
  • Same Gleam on Erlang & JS
  • Comments, order & CRLF survive every write
  • Dynamic decoders for app config
gleam.toml in
# published to Hex on each tag
version = "1.2.0"  # keep in sync with the changelog
gleam.toml out
# published to Hex on each tag
version = "1.3.0"  # keep in sync with the changelog
comments preserved Only the value changed. Both comments stayed put. Final state shown

parse(s) |> to_string == s

An unedited document writes back byte for byte. Comments, key order, string style, integer base, CRLF vs LF: all of it survives the round trip. No reformatting, no surprises in the diff.

# the whole point

Edit a value.
Leave the file alone.

In the BEAM world, most TOML libraries only read — and the rare one that can write hands you a freshly reformatted file with the comments gone. tomlet treats the surrounding text as sacred: it changes the bytes you asked for and nothing else.

before · gleam.toml toml
# package metadata
name = "tomlet"
version = 0
draft = true
three checked edits gleam
use doc <- result.try(tomlet.set_int(doc, ["version"], 1))
use doc <- result.try(tomlet.remove(doc, ["draft"]))
use doc <- result.try(
  tomlet.insert_comment_before(
    doc, ["version"], "first stable release",
  ),
)
after · gleam.toml toml
# package metadata
name = "tomlet"
# first stable releasenew
version = 1

The package metadata header never moved, name is untouched, the new comment landed right where you asked, and draft is simply gone.

Everything you'd want in a
round-tripping parser.

  • Lossless round-trip

    Whitespace, key order, string style and integer base all survive a write — edit a value and nothing around it moves.

  • Editing you can trust

    Typed accessors read by key path and every edit returns a Result, so bad paths and type conflicts come back as matchable errors, never panics.

  • Spec-correct, on both targets

    Parses TOML 1.1 by default with a strict 1.0 mode — pure Gleam, no native dependencies, identical on Erlang and JavaScript and validated against the official toml-test corpora.

Pull out the value
you came for.

Typed accessors take a key path and hand back exactly the type you asked for, including dates and times as opaque values with lexical *_to_string helpers. Mismatch a type and you get a clear, matchable error instead of a guess.

typed access gleam
use doc <- result.try(tomlet.parse(input))

use title   <- result.try(tomlet.get_string(doc, ["title"]))
use version <- result.try(tomlet.get_int(doc, ["version"]))
use enabled <- result.try(tomlet.get_bool(doc, ["enabled"]))

// nested keys use the same path syntax
use name <- result.try(tomlet.get_string(doc, ["pkg", "name"]))

Decode the whole file
into your own shape.

Manual accessors are perfect for a surgical read. When the TOML is your app config, package metadata, or release input, hand tomlet a gleam/dynamic/decode decoder and get back the structure your program already wants.

Tables decode as fields, arrays as lists, and TOML dates and times stay precise through tomlet.date_decoder, tomlet.time_decoder, and tomlet.datetime_decoder.

app.toml toml
name = "tomlet"
released = 1979-05-27T07:32:00Z
targets = ["erlang", "javascript"]
dynamic decoder gleam
import gleam/dynamic/decode
import tomlet

let package_decoder = {
  use name <- decode.field("name", decode.string)
  use released <- decode.field(
    "released", tomlet.datetime_decoder(),
  )
  use targets <- decode.field(
    "targets", decode.list(decode.string),
  )
  decode.success(#(name, released, targets))
}

tomlet.decode(input, package_decoder)

# why it exists

The BEAM didn't have one.

No existing Erlang, Elixir or Gleam TOML library round-trips comments — they parse into plain maps and throw the comments away. Most can only read; the few that can write give you back a reformatted file. If you wanted to bump a version in gleam.toml or rewrite a pyproject.toml as a build step without nuking the author's notes, you were out of luck.

tomlet fills that gap. It's a fresh library with an AST built from the start for lossless round-trip, inspired by Rust's toml_edit and Python's tomlkit, now with a friendly Gleam accent.

gleam add tomlet
Read the docs