---
title: "Choosing a Go YAML library"
url: https://recursiveai.org/oneyaml/articles/golang-yaml/
description: "Golang YAML libraries compared: yaml.v3 is archived and v4 is a release candidate. What go.yaml.in, sigs.k8s.io/yaml, goccy/go-yaml and oneyaml each do."
date: 2026-10-26
author: oneyaml team
---

# Choosing a Go YAML library

Golang YAML libraries compared: yaml.v3 is archived and v4 is a release candidate. What go.yaml.in, sigs.k8s.io/yaml, goccy/go-yaml and oneyaml each do.

October 26, 2026 · oneyaml team

Choosing a Golang YAML library in 2026 starts with the one most Go code already imports: `gopkg.in/yaml.v3` is archived, and its README now opens with “THIS PROJECT IS UNMAINTAINED.” The YAML organization carries it on as `go.yaml.in/yaml/v3`, with a frozen API and security fixes only, and is building `go.yaml.in/yaml/v4`, which was still a release candidate on 26 October 2026. Beyond the go-yaml line, `sigs.k8s.io/yaml` is the reference for Kubernetes-style types with `json` tags, `goccy/go-yaml` adds an AST and YAML Path queries, and oneyaml reads YAML 1.2 and edits a file without disturbing its comments.

Every status line below comes from the project’s own README or module listing, read on 26 October 2026. Behavior was measured by running each library’s published version, and the program is on this page so you can run it yourself.

## Go YAML libraries at a glance

| Module and latest version | Status, per the project | How it works |
|---|---|---|
| [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) v3.0.1 (May 2022) | Repository archived; the [README](https://github.com/go-yaml/yaml/blob/v3/README.md) says “THIS PROJECT IS UNMAINTAINED” | “a pure Go port of the well-known libyaml C library”, with a `yaml.Node` tree |
| [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) v3.0.5 (July 2026) | “Stable, frozen API; security fixes only” ([yaml/go-yaml](https://github.com/yaml/go-yaml)) | The yaml.v3 code under the YAML organization; change the import path and nothing else |
| [go.yaml.in/yaml/v4](https://pkg.go.dev/go.yaml.in/yaml/v4@v4.0.0-rc.6) v4.0.0-rc.6 (June 2026) | “v4.0.0 is not released yet. It is intended to be released in October 2026.” New work happens here | “highly compatible with v3”, plus `Load` and `Dump` with options and plugins |
| [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) v1.6.0 (July 2025) | Maintained by Kubernetes SIGs, “a permanent fork of ghodss/yaml” ([README](https://github.com/kubernetes-sigs/yaml)) | “first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal`” |
| [github.com/goccy/go-yaml](https://pkg.go.dev/github.com/goccy/go-yaml) v1.19.2 (January 2026) | Maintained ([README](https://github.com/goccy/go-yaml)) | Its own parser, written from scratch, with a tokenizer, parser and AST, and YAML Path for filtering and replacing |
| [oneyaml.dev/go](https://pkg.go.dev/oneyaml.dev/go@v1.0.0) v1.0.0 (October 2026) | New: 1.0.0 is its first stable release | Its own parser feeding `Marshal`/`Unmarshal` and a lossless `Document`; YAML 1.2 Core schema; no dependencies, no cgo |

Versions and dates are the latest each module proxy listed on 26 October 2026. go-yaml v4 may ship soon after this was written, so check its README before you decide.

## The same file, read by four Go YAML libraries

The libraries disagree about what plain YAML means. YAML 1.1 read `yes`, `no`, `on` and `off` as booleans and a leading zero as octal. YAML 1.2, the current specification, has only `true` and `false` as booleans and writes octal as `0o755`, so `0755` is the decimal 755. This program reads one three-line file with each library into a `map[string]any`:

*main.go · Go 1.26*

```
package main
 
import (
    "fmt"
 
    goccy "github.com/goccy/go-yaml"
    yamlv3 "go.yaml.in/yaml/v3"
    oneyaml "oneyaml.dev/go"
    k8syaml "sigs.k8s.io/yaml"
)
 
const src = "country: no\nenabled: yes\nmode: 0755\n"
 
func main() {
    decoders := []struct {
        name      string
        unmarshal func([]byte, any) error
    }{
        {"go.yaml.in/yaml/v3", yamlv3.Unmarshal},
        {"sigs.k8s.io/yaml", func(b []byte, v any) error { return k8syaml.Unmarshal(b, v) }},
        {"goccy/go-yaml", goccy.Unmarshal},
        {"oneyaml.dev/go", func(b []byte, v any) error { return oneyaml.Unmarshal(b, v) }},
    }
    for _, d := range decoders {
        var m map[string]any
        if err := d.unmarshal([]byte(src), &m); err != nil {
            fmt.Printf("%-20s error: %v\n", d.name, err)
            continue
        }
        fmt.Printf("%-20s country=%#v enabled=%#v mode=%v\n", d.name, m["country"], m["enabled"], m["mode"])
    }
}
```

*Output*

```
go.yaml.in/yaml/v3   country="no" enabled="yes" mode=493
sigs.k8s.io/yaml     country=false enabled=true mode=493
goccy/go-yaml        country="no" enabled="yes" mode=493
oneyaml.dev/go       country="no" enabled="yes" mode=755
```

- **sigs.k8s.io/yaml** turns `country: no` into `false`, the “Norway problem”, because it reads YAML 1.1 forms before it hands JSON to `encoding/json`.
- **yaml.v3** keeps `yes` and `no` as strings in an untyped map, but its [README](https://github.com/go-yaml/yaml/blob/v3/README.md) says YAML 1.1 booleans “are supported as long as they are being decoded into a typed bool value”, and octals “decode as *0777* per YAML 1.1”. goccy/go-yaml matched it on this file.
- **oneyaml** reads the YAML 1.2 Core schema: two strings and the integer 755. A `yes` read into a `bool` field is a type error with its line and column, not a silent `true`.

The versions measured were go.yaml.in/yaml/v3 v3.0.5, sigs.k8s.io/yaml v1.6.0, goccy/go-yaml v1.19.2 and oneyaml.dev/go v1.0.0. If your files were written for YAML 1.1 readers, the difference matters when you switch, in either direction.

## Read and edit YAML in Go with oneyaml

oneyaml needs Go 1.24 or newer. The import path is `oneyaml.dev/go` and the package name is `oneyaml`:

*Terminal · Go modules*

```
go get oneyaml.dev/go@v1.0.0
```

`Unmarshal` fills a struct by its `yaml` tags, the way `encoding/json` does. To change a file that people also edit by hand, parse it into a `Document` and set values by JSON Pointer:

*main.go · oneyaml.dev/go v1.0.0*

```
package main
 
import (
    "fmt"
    "log"
 
    oneyaml "oneyaml.dev/go"
)
 
type Config struct {
    Name     string `yaml:"name"`
    Replicas int    `yaml:"replicas"`
    Ports    []int  `yaml:"ports"`
}
 
func main() {
    src := []byte(`# Deploy settings
name: api
replicas: 2   # raised by the release job
ports: [80]
`)
    // Typed read, the way encoding/json works.
    var cfg Config
    if err := oneyaml.Unmarshal(src, &cfg); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", cfg)
 
    // Edit the same text: only the values you set change.
    doc, err := oneyaml.Parse(src)
    if err != nil {
        log.Fatal(err)
    }
    if err := doc.Set("/replicas", 3); err != nil {
        log.Fatal(err)
    }
    if err := doc.Set("/ports/-", 443); err != nil {
        log.Fatal(err)
    }
    fmt.Print(doc)
}
```

*Output*

```
{Name:api Replicas:2 Ports:[80]}
# Deploy settings
name: api
replicas: 3   # raised by the release job
ports: [80, 443]
```

The comment after `replicas` and the flow style of `[80]` stay as they were: `Set` rewrites one value in its own style, and a new list item copies the style of its siblings. The [getting started guide](https://recursiveai.org/oneyaml/go/docs/getting-started/) covers writing YAML, patches and errors, and the [struct recipe](https://recursiveai.org/oneyaml/go/cookbook/parse-yaml-into-structs/) and [reading YAML without a struct](https://recursiveai.org/oneyaml/go/cookbook/read-yaml-without-a-struct/) go further.

## What each library is for

### yaml.v3 and go.yaml.in/yaml/v3

The incumbent. Most Go code that reads YAML, and many of your dependencies, import it, and its `yaml.Node` API is documented everywhere. The original module gets no more fixes; `go.yaml.in/yaml/v3` is the same API with security fixes, so switching to it is an import-path change. More in [yaml.v3 is archived: what to use instead](https://recursiveai.org/oneyaml/articles/yaml-v3-alternative/).

### go.yaml.in/yaml/v4

The continuation of the yaml.v3 code base, where its maintainers say new features and routine fixes are developed. It keeps `Unmarshal` and `Marshal`, adds `Load` and `Dump` with options, and ships version presets that restore v3 defaults ([README](https://github.com/yaml/go-yaml)). Until it is released, you are depending on a release candidate.

### sigs.k8s.io/yaml

The way Kubernetes binds manifests: YAML becomes JSON, and your types use `json` tags and `MarshalJSON`/`UnmarshalJSON`. If your types are Kubernetes API types, it is the reference for what a manifest means. Because the trip goes through JSON, comments don’t survive it, and its README warns that map keys that are maps are an error.

### goccy/go-yaml

A from-scratch parser with an AST you can walk and change, YAML Path expressions for filtering and replacing, and readable error messages. Its README reports passing about 60 more yaml-test-suite cases than yaml.v3 (as of 2024-12-15). Choose it when you want to query a document by path.

### oneyaml

YAML 1.2 by default, with `Marshal` and `Unmarshal` that work like `encoding/json` and a `Document` that changes only the values you set: comments and formatting elsewhere stay byte for byte. It applies RFC 6902 JSON Patch and RFC 7396 merge patches to the YAML text, and bounds nesting, alias expansion and number size by default, with an `Untrusted()` preset for input from outside ([limits](https://recursiveai.org/oneyaml/go/docs/limits/)). Adapters for [yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/) and [sigs.k8s.io/yaml](https://recursiveai.org/oneyaml/go/docs/migrate-sigs-k8s-yaml/) keep existing code compiling while you move. Speed and memory against the libraries above are measured on real-world files on the [performance page](https://recursiveai.org/oneyaml/go/docs/performance/), with the method and machine.

## How to choose a Go YAML library

- **Your yaml.v3 code works and you only need a maintained module:** change the import to `go.yaml.in/yaml/v3`.
- **Your types are Kubernetes API types with `json` tags:** sigs.k8s.io/yaml, or oneyaml’s `compat/k8syaml` adapter if you want the same binding with oneyaml underneath.
- **You query documents by path:** goccy/go-yaml. oneyaml’s pointers address one value; it has no query language.
- **You edit YAML files that people maintain, and the diff must hold only your change:** oneyaml. See [edit YAML without losing comments](https://recursiveai.org/oneyaml/articles/edit-yaml-keep-comments/).
- **You apply JSON Patch or merge patches to YAML:** oneyaml applies them to the file itself ([JSON Patch for YAML](https://recursiveai.org/oneyaml/articles/json-patch-yaml/)).
- **You read YAML from outside your program:** oneyaml with `Untrusted()`, which also bounds input size.
- **You need years of field use:** the go-yaml line has them. oneyaml is new, and its first release, 0.1.0, is from October 2026 ([changelog](https://recursiveai.org/oneyaml/go/docs/changelog/)).

For a side-by-side of features, with where the other libraries are the better fit, see [how oneyaml compares](https://recursiveai.org/oneyaml/go/docs/comparison/). All three oneyaml languages are listed on the [oneyaml home page](https://recursiveai.org/oneyaml/).
