CLI tool
BurntSushi/toml avatar
BurntSushi/toml

BurntSushi/toml: a reflection-based TOML decoder for Go

TOML parser for Golang with reflection.

5,012 stars564 forksGoMIT

At a glance

What is it?
A Go package that maps TOML documents onto structs the way encoding/json does, plus a tomlv validator CLI. It suits Go services that need a config file format with types and dates; it is the wrong tool for streaming or for TOML 1.1 features beyond what the decoder implements.
Who is it for?
Adopt BurntSushi/toml if your Go service already reads config through struct tags and you want TOML's typed values and native date handling without a second decoding model. Do not adopt it if you need a decoder that keeps unknown keys addressable after decoding, or if you are writing in Rust or Java, where the TOML story is a different library entirely.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem BurntSushi/toml solves for Go programs

Go's standard library ships encoders for JSON and XML, and both work by reflecting over a struct and matching fields to document keys. TOML had no equivalent in the standard library. BurntSushi/toml fills that gap: the README describes it as a package that "provides a reflection interface similar to Go's standard library json and xml packages." The audience is a Go developer who has chosen TOML as a file format, usually for application configuration, and wants to decode it into a typed struct rather than walk a generic map.

The reason to pick TOML over JSON for config is visible in the README's first example. A file can carry an integer, a list of strings, a float, a list of integers, and an RFC 3339 timestamp, and the corresponding Go struct declares int, []string, float64, []int and time.Time. Dates arrive as time.Time without a custom parse step, and comments are legal in the file, which JSON does not allow. That combination is why TOML turns up in config directories rather than on the wire.

The package also ships a validator. The README documents a CLI tool installed separately, `tomlv`, which takes a TOML file path and checks it. That is a small but real part of the value: you can validate a config file in CI without writing a Go program to do it.

How decoding works: reflection, exported fields and interfaces

The decoding path is the familiar one. You call `toml.Decode` with the raw bytes and a pointer to a struct, and it fills the struct through reflection. Field names map to TOML keys by default, and a struct tag overrides the mapping when the TOML key does not match the Go field name. The README's example pairs the key `some_key_NAME` with the field `ObscureKey` via the tag `toml:"some_key_NAME"`.

The constraint that catches people is stated plainly in the README: like other decoders, only exported fields are considered when encoding and decoding, and private fields are silently ignored. There is no error for a lowercase field that never gets populated. If your config struct has a field you forgot to capitalise, you get a zero value and no complaint, which can look like a missing key in the file rather than a bug in the struct.

For values that need parsing, the package accepts the `encoding.TextUnmarshaler` interface. The README's example defines an `address` type wrapping `*mail.Address`, implements `UnmarshalText` on it, and decodes a TOML array of strings into `[]address`. Each string is handed to `mail.ParseAddress`. A separate `UnmarshalTOML` interface exists for cases where you want to target TOML specifically rather than reuse the text interface. That is the extension point to reach for when a config value is a string in the file but a richer type in your program.

The repository layout is what you would expect for a parser of this kind: separate files for lexing, parsing, decoding, encoding, and error handling, with test data and fuzz tests alongside. The README points to the `_example/` directory for more complex usage rather than reproducing it inline.

Installing BurntSushi/toml and decoding a first file

The README gives the module path directly. The library requires Go 1.19 or newer, and the repository's go.mod declares `go 1.21`, so a module that pulls it in needs a toolchain at least that recent. Add it with:

bash
go get github.com/BurntSushi/toml@latest

If you also want the validator on your PATH, the README documents a second install that builds the CLI from the cmd directory:

bash
go install github.com/BurntSushi/toml/cmd/tomlv@latest

After that, `tomlv some-toml-file.toml` checks the named file. The README does not describe the exit codes or the exact output format, so treat it as a pass or fail gate rather than something to parse.

For a first decode, start from the README's own example. Given a TOML file with an age, a list of cat names, a float, a list of integers and a timestamp:

go
type Config struct {
	Age        int
	Cats       []string
	Pi         float64
	Perfection []int
	DOB        time.Time
}

var conf Config
_, err := toml.Decode(tomlData, &conf)

The second return value is `toml.MetaData`, which the README does not walk through here but which the package exports for callers who need to know more about the decoded document. The struct fields fill from matching keys, the timestamp becomes a `time.Time`, and `err` is nil on a well-formed file. If a key in the file has no matching exported field, the decode does not fail on that account, so compare the populated struct against the file rather than assuming silence means agreement.

Where the reflection model gets in the way

The silent-ignore behaviour is the sharpest limitation, and it is documented rather than hidden. A typo in a TOML key, or a key that simply has no counterpart in the struct, produces no error at decode time. For a config file that is edited by hand across environments, that is the failure mode to plan for: the program starts with a default value and nobody notices the setting never took effect.

The second boundary is format coverage. The README states compatibility with TOML v1.1.0 and links to the spec. It does not claim to be the only or the most complete Go implementation, and it does not document a streaming decoder or a way to decode into an untyped tree and inspect keys afterwards in the README text. If your use case is reading a TOML document whose schema you do not know at compile time, a reflection-first API is a poorer fit than a document-model API.

There is also a version constraint worth noting before you commit: Go 1.19 or newer per the README, and `go 1.21` in go.mod. A codebase pinned to an older toolchain cannot take the current release without a toolchain bump. That is a normal cost, but it is a cost, and it lands on the whole module rather than on the one package.

BurntSushi/toml versus pelletier/go-toml

The comparison people search for is against pelletier/go-toml, and the difference is architectural rather than cosmetic. BurntSushi/toml is built around reflection into your structs, mirroring encoding/json. pelletier/go-toml, particularly in its v2 line, is built around a document tree that you can hold, query and mutate before or instead of binding it to a struct.

That distinction decides the choice. If you have a fixed config schema and a struct that already describes it, BurntSushi/toml's model is the shorter path: declare fields, tag the ones whose names differ, decode. If you need to read a TOML file whose structure you learn at runtime, edit a document and write it back while preserving what you did not touch, or walk keys programmatically, a tree-first library matches that work better. The README for BurntSushi/toml does not present a document-manipulation workflow, which is consistent with the reflection-first design rather than a gap that a future release is promised to close.

Neither choice is about correctness of the TOML grammar. It is about which shape of API your program needs: types first or document first.

Maintenance, releases and the MIT licence

The repository is not archived, and the last push was on 2026-08-18, which is recent relative to the release cadence. Releases are infrequent and deliberate: v1.6.0 on 2025-12-18, v1.5.0 on 2025-03-18, and v1.4.0 on 2024-05-23. The README points to the releases page for a changelog and notes that the same information is in the git tag annotations, so `git show v0.4.0` is the documented way to read a tag's notes.

That cadence shapes the upgrade cost. You are not tracking a stream of point releases; you are moving between versions that arrive roughly every nine months to a year. Because the package is a decoder, an upgrade can change how a document is interpreted, so the practical check after bumping the version is to re-run your config files through `tomlv` and confirm the decode still populates the struct you expect. The README does not document a rollback procedure, so pinning the version in go.mod is the mechanism you control.

Licensing is MIT, and the repository carries a COPYING file at the top level. MIT is permissive and imposes no copyleft obligation on your own code, but the usual condition applies: the licence text and copyright notice travel with distributions of the library. That is a packaging detail, not a design constraint. Nothing here is legal advice; read COPYING if the terms matter to your organisation.

Editorial conclusion

Adopt BurntSushi/toml if your Go service already reads config through struct tags and you want TOML's typed values and native date handling without a second decoding model. Do not adopt it if you need a decoder that keeps unknown keys addressable after decoding, or if you are writing in Rust or Java, where the TOML story is a different library entirely. Before committing, verify two things against your own config files: that every key you rely on maps to an exported struct field, since private fields are silently ignored, and that the TOML 1.1 constructs you use are covered by the version you pin, because the README states compatibility with TOML v1.1.0 but does not enumerate what that covers.

Frequently asked questions

What does TOML stand for in BurntSushi/toml?

The README expands it as Tom's Obvious, Minimal Language. The package is a Go implementation of that format, exposing a reflection interface similar to the standard library's json and xml packages.

What does a config TOML file do in a Go project?

It holds typed settings that the package decodes into a Go struct. The README's example maps an integer, a string list, a float, an integer list and an RFC 3339 timestamp onto int, []string, float64, []int and time.Time fields.

Is TOML widely used?

The README does not make any claim about adoption, and no usage figures appear in the repository material. What it does state is that this package is compatible with TOML v1.1.0 and that it requires Go 1.19 or newer.

How does BurntSushi/toml compare with pelletier/go-toml?

BurntSushi/toml decodes through reflection into structs, following the encoding/json model. pelletier/go-toml is built around a document tree you can query and mutate, which suits reading TOML whose schema you do not know at compile time.

Official sources

  1. BurntSushi/toml on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/burntsushi-toml.svg)](https://hysenlabs.com/projects/burntsushi-toml)