tree: 6650b035a9ce57494c925a2b0de62358a9039025 [path history] [tgz]
  1. error.go
  2. ini.go
  3. key.go
  5. parser.go
  8. section.go
  9. struct.go

INI Build Status Sourcegraph

Package ini provides INI file read and write functionality in Go.



  • Load multiple data sources([]byte, file and io.ReadCloser) with overwrites.
  • Read with recursion values.
  • Read with parent-child sections.
  • Read with auto-increment key names.
  • Read with multiple-line values.
  • Read with tons of helper methods.
  • Read and convert values to Go types.
  • Read and WRITE comments of sections and keys.
  • Manipulate sections, keys and comments with ease.
  • Keep sections and keys in order as you parse and save.


To use a tagged revision:

go get

To use with latest changes:

go get

Please add -u flag to update in the future.


If you want to test on your machine, please apply -t flag:

go get -t

Please add -u flag to update in the future.

Getting Started

Loading from data sources

A Data Source is either raw data in type []byte, a file name with type string or io.ReadCloser. You can load as many data sources as you want. Passing other types will simply return an error.

cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))

Or start with an empty object:

cfg := ini.Empty()

When you cannot decide how many data sources to load at the beginning, you will still be able to Append() them later.

err := cfg.Append("other file", []byte("other raw data"))

If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use LooseLoad to ignore nonexistent files without returning error.

cfg, err := ini.LooseLoad("filename", "filename_404")

The cool thing is, whenever the file is available to load while you're calling Reload method, it will be counted as usual.

Ignore cases of key name

When you do not care about cases of section and key names, you can use InsensitiveLoad to force all names to be lowercased while parsing.

cfg, err := ini.InsensitiveLoad("filename")

// sec1 and sec2 are the exactly same section object
sec1, err := cfg.GetSection("Section")
sec2, err := cfg.GetSection("SecTIOn")

// key1 and key2 are the exactly same key object
key1, err := cfg.GetKey("Key")
key2, err := cfg.GetKey("KeY")

MySQL-like boolean key

MySQL's configuration allows a key without value as follows:


By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:

cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))

The value of those keys are always true, and when you save to a file, it will keep in the same foramt as you read.

To generate such keys in your program, you could use NewBooleanKey:

key, err := sec.NewBooleanKey("skip-host-cache")


Take care that following format will be treated as comment:

  1. Line begins with # or ;
  2. Words after # or ;
  3. Words after section name (i.e words after [some section name])

If you want to save a value with # or ;, please quote them with ` or """.

Working with sections

To get a section, you would need to:

section, err := cfg.GetSection("section name")

For a shortcut for default section, just give an empty string as name:

section, err := cfg.GetSection("")

When you're pretty sure the section exists, following code could make your life easier:

section := cfg.Section("section name")

What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.

To create a new section:

err := cfg.NewSection("new section")

To get a list of sections or section names:

sections := cfg.Sections()
names := cfg.SectionStrings()

Working with keys

To get a key under a section:

key, err := cfg.Section("").GetKey("key name")

Same rule applies to key operations:

key := cfg.Section("").Key("key name")

To check if a key exists:

yes := cfg.Section("").HasKey("key name")

To create a new key:

err := cfg.Section("").NewKey("name", "value")

To get a list of keys or key names:

keys := cfg.Section("").Keys()
names := cfg.Section("").KeyStrings()

To get a clone hash of keys and corresponding values:

hash := cfg.Section("").KeysHash()

Working with values

To get a string value:

val := cfg.Section("").Key("key name").String()

To validate key value on the fly:

val := cfg.Section("").Key("key name").Validate(func(in string) string {
	if len(in) == 0 {
		return "default"
	return in

If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):

val := cfg.Section("").Key("key name").Value()

To check if raw value exists:

yes := cfg.Section("").HasValue("test value")

To get value with types:

// For boolean values:
// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("UINT").Uint()
v, err = cfg.Section("").Key("UINT64").Uint64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339

v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
v = cfg.Section("").Key("INT64").MustInt64()
v = cfg.Section("").Key("UINT").MustUint()
v = cfg.Section("").Key("UINT64").MustUint64()
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
v = cfg.Section("").Key("TIME").MustTime() // RFC3339

// Methods start with Must also accept one argument for default value
// when key not found or fail to parse value to given type.
// Except method MustString, which you have to pass a default value.

v = cfg.Section("").Key("String").MustString("default")
v = cfg.Section("").Key("BOOL").MustBool(true)
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
v = cfg.Section("").Key("INT").MustInt(10)
v = cfg.Section("").Key("INT64").MustInt64(99)
v = cfg.Section("").Key("UINT").MustUint(3)
v = cfg.Section("").Key("UINT64").MustUint64(6)
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339

What if my value is three-line long?

ADDRESS = """404 road,
NotFound, State, 5000

Not a problem!


/* --- start ---
404 road,
NotFound, State, 5000
------  end  --- */

That's cool, how about continuation lines?

two_lines = how about \
	continuation lines?
lots_of_lines = 1 \
	2 \
	3 \

Piece of cake!

cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4

Well, I hate continuation lines, how do I disable that?

cfg, err := ini.LoadSources(ini.LoadOptions{
	IgnoreContinuation: true,
}, "filename")

Holy crap!

Note that single quotes around values will be stripped:

foo = "some value" // foo: some value
bar = 'some value' // bar: some value

That's all? Hmm, no.

Helper methods of working with values

To get value with given candidates:

v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339

Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.

To validate value in a given range:

vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
Auto-split values into a slice

To use zero value of type for invalid inputs:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("UINTS").Uints(",")
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")

To exclude invalid values out of result slice:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> [2.2]
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
vals = cfg.Section("").Key("INTS").ValidInts(",")
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
vals = cfg.Section("").Key("UINTS").ValidUints(",")
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
vals = cfg.Section("").Key("TIMES").ValidTimes(",")

Or to return nothing but error when have invalid inputs:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> error
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
vals = cfg.Section("").Key("INTS").StrictInts(",")
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
vals = cfg.Section("").Key("UINTS").StrictUints(",")
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
vals = cfg.Section("").Key("TIMES").StrictTimes(",")

Save your configuration

Finally, it's time to save your configuration to somewhere.

A typical way to save configuration is writing it to a file:

// ...
err = cfg.SaveTo("my.ini")
err = cfg.SaveToIndent("my.ini", "\t")

Another way to save is writing to a io.Writer interface:

// ...
cfg.WriteToIndent(writer, "\t")

By default, spaces are used to align “=” sign between key and values, to disable that:

ini.PrettyFormat = false

Advanced Usage

Recursive Values

For all value of keys, there is a special syntax %(<name>)s, where <name> is the key name in same section or default section, and %(<name>)s will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.

NAME = ini

NAME = Unknwon

cfg.Section("author").Key("GITHUB").String()		//
cfg.Section("package").Key("FULL_NAME").String()	//

Parent-child Sections

You can use . in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.

NAME = ini


cfg.Section("package.sub").Key("CLONE_URL").String()	//

Retrieve parent keys available to a child section

cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]

Unparseable Sections

Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use LoadOptions.UnparsableSections:

cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))

body := cfg.Section("COMMENTS").Body()

/* --- start ---
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
------  end  --- */

Auto-increment Key Names

If key name is - in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.

-: Support read/write comments of keys and sections
-: Support auto-increment of key names
-: Support load multiple files to overwrite key values
cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}

Map To Struct

Want more objective way to play with INI? Cool.

Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z

Content = Hi is a good man!
Cities = HangZhou, Boston
type Note struct {
	Content string
	Cities  []string

type Person struct {
	Name string
	Age  int `ini:"age"`
	Male bool
	Born time.Time
	Created time.Time `ini:"-"`

func main() {
	cfg, err := ini.Load("path/to/ini")
	// ...
	p := new(Person)
	err = cfg.MapTo(p)
	// ...

	// Things can be simpler.
	err = ini.MapTo(p, "path/to/ini")
	// ...

	// Just map a section? Fine.
	n := new(Note)
	err = cfg.Section("Note").MapTo(n)
	// ...

Can I have default value for field? Absolutely.

Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.

// ...
p := &Person{
	Name: "Joe",
// ...

It‘s really cool, but what’s the point if you can't give me my file back from struct?

Reflect From Struct

Why not?

type Embeded struct {
	Dates  []time.Time `delim:"|"`
	Places []string    `ini:"places,omitempty"`
	None   []int       `ini:",omitempty"`

type Author struct {
	Name      string `ini:"NAME"`
	Male      bool
	Age       int
	GPA       float64
	NeverMind string `ini:"-"`

func main() {
	a := &Author{"Unknwon", true, 21, 2.8, "",
			[]time.Time{time.Now(), time.Now()},
			[]string{"HangZhou", "Boston"},
	cfg := ini.Empty()
	err = ini.ReflectFrom(cfg, a)
	// ...

So, what do I get?

NAME = Unknwon
Male = true
Age = 21
GPA = 2.8

Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
places = HangZhou,Boston

Name Mapper

To save your time and make your code cleaner, this library supports NameMapper between struct field and actual section and key name.

There are 2 built-in name mappers:

  • AllCapsUnderscore: it converts to format ALL_CAPS_UNDERSCORE then match section or key.
  • TitleUnderscore: it converts to format title_underscore then match section or key.

To use them:

type Info struct {
	PackageName string

func main() {
	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
	// ...

	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
	// ...
	info := new(Info)
	cfg.NameMapper = ini.AllCapsUnderscore
	err = cfg.MapTo(info)
	// ...

Same rules of name mapper apply to ini.ReflectFromWithMapper function.

Value Mapper

To expand values (e.g. from environment variables), you can use the ValueMapper to transform values:

type Env struct {
	Foo string `ini:"foo"`

func main() {
	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
	cfg.ValueMapper = os.ExpandEnv
	// ...
	env := &Env{}
	err = cfg.Section("env").MapTo(env)

This would set the value of env.Foo to the value of the environment variable MY_VAR.

Other Notes On Map/Reflect

Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:

type Child struct {
	Age string

type Parent struct {
	Name string

type Config struct {
	City string

Example configuration:

City = Boston

Name = Unknwon

Age = 21

What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.

type Child struct {
	Age string

type Parent struct {
	Name string
	Child `ini:"Parent"`

type Config struct {
	City string

Example configuration:

City = Boston

Name = Unknwon
Age = 21

Getting Help


What does BlockMode field do?

By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set cfg.BlockMode = false to speed up read operations about 50-70% faster.

Why another INI library?

Many people are using my another INI library goconfig, so the reason for this one is I would like to make more Go style code. Also when you set cfg.BlockMode = false, this one is about 10-30% faster.

To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using to version my package at this time.(PS: shorter import path)


This project is under Apache v2 License. See the LICENSE file for the full license text.