Golang Read YAML File: Parse YAML into Struct or Map

Deepak Prasad
Tested on RHEL 10.2 with Go 1.26.5
Package go 1.26.5
go.yaml.in/yaml/v4 v4.0.0-rc.6
Applies to Any host with Go 1.23+ installed
Privilege Normal user
Scope Read and parse YAML files in Go: package choice (legacy gopkg.in vs go.yaml.in), Load into structs and maps, nested objects and lists, multi-document loading, WithKnownFields strict parsing, and common errors. Does not cover Helm, Kubernetes manifests, or full YAML specification.
Related guides Parse JSON in Go
JSON Unmarshal in Go
Golang Viper
Type assertions in Go
Getting started with Go

YAML is not part of the Go standard library. To read a YAML file in Go you add a third-party module, load the bytes, and decode them into a typed value. When you know the schema—a config file, CI settings, or service definitions—a struct with yaml tags is usually the clearest model. A map[string]any is better when keys are unknown or the document is truly dynamic.

This walkthrough uses go.yaml.in/yaml/v4 with the current Load / NewLoader API and covers struct parsing, nested lists, dynamic maps, multi-document streams, and strict field checks.


Which YAML package should you use in Go?

Import path Status
gopkg.in/yaml.v3 Historical import many modules still use. Frozen legacy branch with security fixes only.
go.yaml.in/yaml/v3 Same v3 API under the maintained module path. Also frozen with security fixes only.
go.yaml.in/yaml/v4 Active development line. Recommended for new projects; still a release candidate (v4.0.0-rc.6 at time of writing).

v1, v2, and v3 remain available as frozen legacy lines. You do not need to rush migration if stable v3 code already works—plan a move to go.yaml.in/yaml/v4 when you want current features or are starting a new module. See the v3 to v4 migration guide for import-path and API changes. v4 still exposes yaml.Unmarshal and yaml.NewDecoder as compatibility APIs; the examples below use yaml.Load and yaml.NewLoader, which upstream recommends for new v4 code.


Install the YAML package

Create a module if you do not have one yet:

bash
go mod init yamlapp

go mod init writes a new go.mod in the current directory.

Add the maintained v4 release used in this article:

bash
go get go.yaml.in/yaml/v4@v4.0.0-rc.6

The added line in the output confirms the module resolved. Import it as:

go
import "go.yaml.in/yaml/v4"
Input shape Recommended destination
Known schema Struct with yaml tags
Dynamic or unknown keys map[string]any
Multiple --- documents in one file yaml.NewLoader + loop Load until io.EOF
Known struct schema where unknown keys must fail yaml.WithKnownFields() on Load / NewLoader into a struct

Read a YAML file into a Go struct

Most golang parse yaml file tasks follow one path: read bytes from disk, load into a struct, then use typed fields.

Create config.yaml beside your program:

yaml
name: orders-api
port: 8080
enabled: true
database:
  host: db.internal
  port: 5432

Define a struct whose exported fields and tags match the YAML keys, then load:

go
package main

import (
	"fmt"
	"os"

	"go.yaml.in/yaml/v4"
)

type Database struct {
	Host string `yaml:"host"`
	Port int    `yaml:"port"`
}

type Config struct {
	Name     string   `yaml:"name"`
	Port     int      `yaml:"port"`
	Enabled  bool     `yaml:"enabled"`
	Database Database `yaml:"database"`
}

func main() {
	b, err := os.ReadFile("config.yaml")
	if err != nil {
		panic(err)
	}
	var cfg Config
	if err := yaml.Load(b, &cfg); err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", cfg)
	fmt.Println("db host:", cfg.Database.Host)
}

Run it from the directory that contains config.yaml:

bash
go run .

Sample output:

output
{Name:orders-api Port:8080 Enabled:true Database:{Host:db.internal Port:5432}}
db host: db.internal

A successful load does not prove every required setting is present—missing keys become Go zero values ("", 0, false). Validate business rules after parsing.

YAML tags and exported fields

YAML keys are often lowercase or snake_case. Map them with tags such as Port int `yaml:"port"` or NodeName string `yaml:"node-name"` when the wire name differs from the Go field name.

Only exported (capitalized) fields participate in loading. Lowercase fields are invisible to the decoder, which looks like “YAML ignored half my struct.” The same rule applies to JSON Unmarshal in Go.


Parse nested YAML and lists

Extend the config shape with nested objects and sequences. YAML mappings become structs (or nested struct literals), and YAML sequences become slices:

YAML shape Go type
mapping / object struct
sequence slice ([]string, []Server, …)
nested mapping nested struct
optional subtree where nil matters pointer field (*Database)

Add list fields to the struct from the previous section:

go
type Server struct {
	Host string `yaml:"host"`
	Port int    `yaml:"port"`
}

type Config struct {
	Name     string   `yaml:"name"`
	Servers  []string `yaml:"servers"`
	Database struct {
		Host string `yaml:"host"`
		SSL  bool   `yaml:"ssl"`
	} `yaml:"database"`
	Backends []Server `yaml:"backends"`
}

Load YAML that includes nested database and list keys:

yaml
name: edge
servers:
  - api-1
  - api-2
database:
  host: postgres.local
  ssl: true
backends:
  - host: cache-1
    port: 6379
  - host: cache-2
    port: 6379

After yaml.Load, len(cfg.Servers) is 2, cfg.Database.Host is postgres.local, and cfg.Backends[0].Port is 6379. Lists of scalars map to []string or []int; lists of objects map to []YourStruct.


Parse YAML into map[string]any

When the schema is unknown—exploratory tooling, loosely defined documents, or keys you cannot freeze at compile time—load into map[string]any:

go
const src = `
name: probe
labels:
  env: staging
  team: platform
`
var m map[string]any
if err := yaml.Load([]byte(src), &m); err != nil {
	panic(err)
}
fmt.Println("name:", m["name"])
labels := m["labels"].(map[string]any)
fmt.Println("env:", labels["env"])

Nested maps decode as map[string]any; sequences decode as []any. You walk the tree with type assertions (see type assertions in Go).

Maps trade compile-time safety for flexibility. Dynamic scalar types depend on the YAML value and the destination type—they are not the same as encoding/json, where numbers in map[string]any commonly become float64. For compatibility, go-yaml recognizes YAML 1.1 words such as yes/no and on/off when decoding into typed bool fields; otherwise they remain strings. Watch unquoted numeric-looking values: bare 0123 can parse as an octal integer rather than the string you meant. If an ID, ZIP code, or phone number must remain text, model it as string in a struct or quote it in the YAML ("0123").


Decode multiple YAML documents and reject unknown fields

yaml.NewLoader reads from an io.Reader. Use it when a file or stream contains multiple YAML documents separated by ---. For a single document that fits in memory, os.ReadFile plus yaml.Load is enough.

Define a small struct for each document:

go
type Service struct {
	Port int    `yaml:"port"`
	Host string `yaml:"host"`
}

Multiple documents

If the whole multi-document file already fits in memory, v4 can also load all documents into a slice with yaml.Load(data, &docs, yaml.WithAllDocuments()). Use yaml.NewLoader when you want to process documents one at a time from a stream.

Loop until io.EOF—each Load call reads one document:

go
loader, err := yaml.NewLoader(reader)
if err != nil {
	return err
}
for {
	var doc Service
	err := loader.Load(&doc)
	if err == io.EOF {
		break
	}
	if err != nil {
		return err
	}
	process(doc)
}

Strict fields with WithKnownFields

By default, loading ignores unknown keys—a typo such as porrt leaves Port at 0 with no error. Pass yaml.WithKnownFields() when mistyped keys must fail:

go
bad := strings.NewReader("porrt: 8080\nhost: api.local\n")
loader, err := yaml.NewLoader(bad, yaml.WithKnownFields())
if err != nil {
	panic(err)
}
var s Service
if err := loader.Load(&s); err != nil {
	fmt.Println(err)
}

A typo in the key name produces a construct error instead of silently leaving Port at zero:

text
yaml: construct errors: line 1: field porrt not found in type main.Service

Still validate required business fields in Go after a successful load.


Common YAML parsing errors

Problem Likely reason
no such file or directory Wrong path or working directory when calling os.ReadFile
Fields remain empty Fields are unexported or yaml tags do not match YAML keys
cannot unmarshal string into int Scalar type in the file does not match the Go field type
cannot unmarshal mapping into slice YAML object where the struct expects a list (or the reverse)
field not found in type yaml.WithKnownFields() caught a typo or unsupported key
Required value is empty after parsing Key was missing; Go left the field at its zero value
yaml: line N: did not find expected ... Indentation, colons, or quoting syntax error in the file

Check both the file read error and the load error. Fix YAML syntax in an editor with YAML linting when line numbers appear in the message.


Summary

This article uses the v4 Load API; yaml.Unmarshal remains available for simple decoding without options. Choose a maintained module (go.yaml.in/yaml/v4 for new work, with gopkg.in/yaml.v3 as the legacy import path), read bytes with os.ReadFile, and decode into a struct or map. Structs with exported fields and yaml tags are the default for known config; nested objects and lists map to nested structs and slices. Use map[string]any only when the document is dynamic, and quote or type scalars carefully when string form matters.

For multiple --- documents, load into a slice with yaml.WithAllDocuments() or stream with yaml.NewLoader and loop until io.EOF. For a known struct schema where typos must fail, pass yaml.WithKnownFields(). Pair parsing with application-level validation so missing keys do not slip through as zero values. For JSON on the same codebase, see Parse JSON in Go.


References


Frequently Asked Questions

1. Which YAML package should I use in Go in 2026?

Use go.yaml.in/yaml/v4 for new work. The historical import gopkg.in/yaml.v3 is a frozen legacy branch; upstream development moved to the YAML organization and recommends v4 for new projects, though v4 is still a release candidate as of 2026.

2. Is gopkg.in/yaml.v3 still maintained?

The original go-yaml/yaml repository is unmaintained. gopkg.in/yaml.v3 and go.yaml.in/yaml/v3 remain frozen legacy lines with security fixes only; active features and bug fixes land in go.yaml.in/yaml/v4.

3. How do I parse YAML into a Go struct?

Read the file with os.ReadFile, define an exported struct with yaml tags that match the keys, then call yaml.Load on the bytes into a struct variable.

4. Can I parse YAML without creating a struct?

Yes. Load into map[string]any when the schema is dynamic, then use type assertions to read nested values. Structs are safer when you know the shape of the file.

5. How do I reject unknown YAML keys?

Pass yaml.WithKnownFields() to yaml.Load or yaml.NewLoader, then load into your struct. A typo such as porrt instead of port returns an error instead of leaving Port at zero.

6. How do I read multiple YAML documents separated by ---?

Create yaml.NewLoader on an io.Reader and call Load in a loop until io.EOF. Each call reads one document from the stream.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.

  • Go (programming language)
  • Python (programming language)
  • Java (programming language)
  • MongoDB
  • Kubernetes