CLI tool
knadh/koanf avatar
knadh/koanf

knadh/koanf: a small Go config library where providers and parsers are separate packages

Simple, extremely lightweight, extensible, configuration management library for Go. Supports JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

4,212 stars207 forksGoMIT

At a glance

What is it?
koanf reads configuration from files, environment variables, flags, S3, Vault and more, but ships almost nothing in its core. Here is how the provider and parser split works, how to install it, and when viper is still the better pick.
Who is it for?
Adopt koanf if you want to compose configuration from several sources and keep the dependency tree small, and if you are willing to install a provider and a parser for each format you use. Do not adopt it expecting viper's single-package convenience or its built-in remote key/value wiring.
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 5 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.

DEEP OPEN-SOURCE ANALYSIS

The problem koanf solves: configuration from many sources without a fat core

A Go service usually ends up reading configuration from more than one place. Defaults live in a struct or a map, a TOML or YAML file holds the deployment-specific values, environment variables override those in containers, and command line flags override everything when an operator needs to change one setting at runtime. Doing this by hand means writing the same merge logic in every project.

koanf exists to make that merge a library call. The README describes it as "a cleaner, lighter alternative to spf13/viper with better abstractions and extensibility and far fewer dependencies." The audience is Go engineers who already know they need layered configuration and want to pick the layers themselves.

The second half of the problem is dependency weight. A configuration library that bundles S3, Vault, Consul, etcd and six parsers pulls all of that into your module graph whether you use it or not. koanf's answer is to keep the core almost empty. The go.mod for the core module lists three direct requirements: github.com/go-viper/mapstructure/v2, github.com/knadh/koanf/maps and github.com/mitchellh/copystructure. Everything else lives in separate modules under providers/ and parsers/.

How koanf works: providers, parsers and a delimited key path

The whole design rests on two interfaces, described in the README's Concepts section. A koanf.Provider supplies configuration, either as raw bytes or as a nested map[string]any. A koanf.Parser takes raw bytes and returns a nested map[string]any. A file provider plus a YAML parser gives you YAML from disk. The same file provider plus a TOML parser gives you TOML from disk. The provider does not know about the format and the parser does not know where the bytes came from.

Values are read back with a delimited key path, for example app.server.port. The delimiter is chosen when the instance is created. The README's example uses koanf.New(".") and notes that the delimiter "can be '/' or any character." That choice is fixed for the lifetime of the instance, so a key containing a dot in a source format needs a delimiter that avoids the collision.

Loading is additive. Configuration from multiple sources can be loaded and merged into one instance, and the README gives the canonical case: load from a file first, then override certain values with command line flags. Each Load call merges into what is already there. That is the entire data flow. There is no schema, no registry, no global state beyond the instance you create.

Watching is opt-in per provider. The README states that file, appconfig, vault and consul providers expose a Watch() method that triggers a callback on change. It also states plainly that this "is not goroutine safe if there are concurrent *Get() calls happening on the koanf object while it is doing a Load()", and that such scenarios need mutex locking. That is a real constraint, not a footnote. If your service reads config on every request and you also watch a file, you own the locking.

Installing koanf and reading a first config file

The core and each provider and parser are separate go get targets. The README shows the core install, then a file provider install, then a TOML parser install, with comments listing the available providers (file, env/v2, posflag, basicflag, confmap, rawbytes, structs, fs, s3, appconfig/v2, consul/v2, etcd/v2, vault/v2, parameterstore/v2) and parsers (toml, toml/v2, json, yaml, huml, dotenv, hcl, hjson, nestedtext).

bash
go get -u github.com/knadh/koanf/v2
go get -u github.com/knadh/koanf/providers/file
go get -u github.com/knadh/koanf/parsers/toml

The minimal program creates one instance, loads a JSON file, then loads a YAML file on top of it. The README's file example uses a dot delimiter and prints two values from the merged result.

go
var k = koanf.New(".")

if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
	log.Fatalf("error loading config: %v", err)
}

k.Load(file.Provider("mock/mock.yml"), yaml.Parser())

fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))

After this runs, k.String("parent1.name") returns the value from the JSON file unless the YAML file defines the same key, in which case the YAML value wins because it was loaded second. The typed getters (String, Int and the rest) are what you call at the point of use; there is no struct binding required to read a value.

For live reload, the file provider's Watch method takes a callback. The README's example discards the old instance inside the callback and builds a fresh one, then calls k.Print(). It also notes that the file provider always returns a nil event, and that f.Unwatch() stops the watcher.

Where koanf gets awkward: concurrency, merge surprises and missing providers

The goroutine safety note is the first limitation worth taking seriously. Watch plus concurrent getters needs a mutex you write yourself. A configuration library that reloads in the background while requests read keys is exactly the situation where a race detector run matters, and koanf hands that responsibility to the caller.

The second issue is merge order. Because loading is additive and silent, a key defined in two sources resolves to whichever was loaded last. That is the documented behaviour, but it means a typo in a flag name does not raise an error; it simply adds a new key that nothing reads. Nothing in the README describes a schema or a required-key check, so validation is your code's job.

The third is coverage. The provider list is long but finite. If your configuration lives in a system that is not on it, you write a Provider implementation. The README documents that extension point under Custom Providers and Parsers, and it is a small interface, but it is code you now maintain.

One more practical point: the core module's go.mod carries a retract directive for v2.0.2, tagged as a minor version that "contains breaking changes." If your tooling resolves that version, the retraction is how the module tells you to move off it.

koanf vs viper: the difference is where the dependencies live

The comparison the README invites is with spf13/viper, and it is the comparison people search for. The architectural difference is packaging. Viper is one module that brings its parsers, its remote providers and its dependency set with it. koanf splits the same capabilities into a core module plus one module per provider and one per parser, so a service that reads TOML from disk and env vars from the process imports exactly those.

That split has a cost. With viper you add one import path and get everything. With koanf you add the core, a provider, and a parser, and you repeat that for each source. The README's install section spells out this repetition deliberately, listing the provider and parser names as comments so you know what exists.

There is also a difference in how the two handle the merge. koanf exposes Load as an explicit, ordered operation on a plain instance, and the README documents the order of merge and key case sensitivity as its own topic. If you want to reason about precedence by reading your main function, that model is easier to follow than a library that decides precedence internally.

Neither approach is wrong. If you want the smallest possible module graph and you are comfortable assembling pieces, koanf's split is the point of the project. If you want one import and a large default feature set, viper is the shorter path.

Maintenance, licensing and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-02. The most recent tagged release listed is v2.3.6 from 2026-08-04, preceded by v2.3.5 on 2026-05-30 and v2.3.4 on 2026-03-21. That is a steady patch cadence on the v2 line.

The core module declares go 1.23.0, so the toolchain floor is explicit. Upgrades are per-module: bumping a parser does not force a bump of the core, and vice versa. That is the practical benefit of the split, and it is also the upgrade chore, since a project with three providers and two parsers has five version lines to track.

koanf is MIT licensed. The README does not discuss licence implications for downstream users, and the repository's LICENSE file is the authoritative text. If you vendor or redistribute it, read that file rather than any summary, and treat the provider modules as separate works to check.

Editorial conclusion

Adopt koanf if you want to compose configuration from several sources and keep the dependency tree small, and if you are willing to install a provider and a parser for each format you use. Do not adopt it expecting viper's single-package convenience or its built-in remote key/value wiring. Before committing, read the API list in the README to confirm a provider exists for your source, and test the merge order you intend to rely on, since the README documents it but does not enforce it for you.

Frequently asked questions

How does koanf differ from viper in Go?

The README calls koanf a cleaner, lighter alternative to spf13/viper with better abstractions and extensibility and far fewer dependencies. The concrete difference is packaging: koanf keeps its core small and ships each provider and parser as a separately installed module, while viper bundles its feature set into one package.

How do I install koanf and a file provider?

You install the core with go get -u github.com/knadh/koanf/v2, then install each provider and parser you need, for example go get -u github.com/knadh/koanf/providers/file and go get -u github.com/knadh/koanf/parsers/toml. The README lists the available provider and parser names in comments next to those commands.

Which providers and parsers does koanf ship?

The README lists providers for file, env/v2, posflag, basicflag, confmap, rawbytes, structs, fs, s3, appconfig/v2, consul/v2, etcd/v2, vault/v2 and parameterstore/v2. Parsers cover toml, toml/v2, json, yaml, huml, dotenv, hcl, hjson and nestedtext.

Is koanf safe to use with file watching and concurrent reads?

The README states that watching is not goroutine safe if there are concurrent Get calls happening on the koanf object while it is doing a Load, and that such scenarios need mutex locking. The file, appconfig, vault and consul providers expose a Watch method.

What licence does koanf use?

The repository is MIT licensed. The README does not discuss licence implications, so the LICENSE file in the repository is the text to read before redistributing or vendoring it.

Official sources

  1. Issues
  2. knadh/koanf on GitHub
  3. License: MIT
  4. README
  5. Releases
For maintainers

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/knadh-koanf.svg)](https://hysenlabs.com/projects/knadh-koanf)
Community notes

Community notes