# go.yaml.in/yaml: the YAML organisation takes over the Go YAML library

> go.yaml.in/yaml is the YAML organisation's fork of the go-yaml library, taken over after the original author marked it unmaintained in April 2025. The package itself is familiar, with v1, v2 and v3 frozen for security fixes only and all new work landing in v4, so the interesting decision for a Go user is which import path to bet on.

**yaml/go-yaml** — The YAML org maintained fork of https://github.com/go-yaml/yaml

- Repository: https://github.com/yaml/go-yaml
- Stars: 542 · Forks: 81
- Language: Go
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/yaml-go-yaml

## A fork taken over after a maintainer stepped back

The history is unusual enough that it changes how you read the version numbers.

The package was originally developed within Canonical as part of the juju project, and is based on a pure Go port of the libyaml C library. The readme says that port exists to parse and generate YAML data quickly and reliably, which is the origin of the library's reputation for handling the awkward parts of the specification.

Then it became go-yaml, and for years that was the de facto YAML library for Go. The readme states that the YAML team took over ongoing maintenance and development after discussion with go-yaml's author, @niemeyer, following his decision to label the project repository as unmaintained in April 2025. So the fork is not a dispute about the code, it is a transfer of stewardship after the original maintainer stepped away.

The readme also says the team put together a group of dedicated maintainers including representatives of go-yaml's most important downstream projects, and states the goal plainly: to earn the trust of the various go-yaml forks to switch back to this repository as their upstream. In other words, the project is in the middle of a consolidation, and the direction of travel is that other forks will merge here.

The last push to the repository was on 2026-10-20, so this is a live, actively worked repository rather than an archive, and contributions are handled through a Slack channel linked from the readme.

## v1, v2 and v3 are frozen, everything new happens in v4

The version policy is the most important operational detail in the readme, and it is unusual for a library of this age.

Versions v1, v2 and v3 remain as frozen legacy. They will receive security fixes only, so existing consumers keep working without breaking changes. All ongoing work, including new features and routine bug fixes, will happen in v4, and if you are starting a new project or upgrading an existing one, the readme asks you to use the go.yaml.in/yaml/v4 import path.

The consequence is that this is not a drop-in upgrade. The import path changes, and with it the module path, since the go.mod in the repository declares the module as go.yaml.in/yaml/v4. A project that upgrades has to rewrite its imports, and the older major versions continue to exist on their own terms for anyone who stays put.

That is a deliberate design. Freezing three major versions is unusual work for a maintainer team to take on, and it is only possible because the alternative, closing v3 to everything but security fixes, would break a very large number of downstream users. The readme separately notes that the package API for v3 will remain stable as described in the gopkg.in stability document, which is a promise about v3 specifically, not about the package as a whole.

For a team deciding what to do, the practical framing is this. Staying on v3 means no new features and no behavioural changes, only security patches. Moving to v4 means a rename of the import path and access to the ongoing work. There is no in-between in the sense of getting v4 features on the v3 path.

## YAML 1.2 with a few 1.1 behaviours kept on purpose

The compatibility section is where the library's real behaviour lives, and the readme is unusually specific about it.

The package supports most of YAML 1.2 but preserves some behaviour from 1.1 for backwards compatibility, and the text says specifically of v3 that this is the case. Three items are named.

First, YAML 1.1 booleans. The values yes, no, on and off are supported as long as they are being decoded into a typed bool value; otherwise they behave as a string. YAML 1.2 booleans are true and false only. So the same document can produce a bool or a string depending on the Go type you decode into, which is a sharp edge worth knowing about before you write a struct with a bool field and expect yes to work.

Second, octal notation. The package encodes and decodes 0777 as YAML 1.1 specifies, rather than 0o777 as YAML 1.2 specifies, because most parsers still use the old format. The 0o777 format is supported too, so new files work. That is a compatibility hedge in both directions and it means a document written by one tool and read by another will round trip, whichever octal style each uses.

Third, base-60 floats are not supported. The readme notes these are gone from YAML 1.2 and were in fact never supported by this package, describing them as clearly a poor choice. So this is not a regression you need to work around, but it is worth knowing if you have YAML written by a system that emitted sexagesimal numbers.

The overall picture is a library that tracks 1.2 for new work while keeping the 1.1 habits that exist in real files. For anyone writing a validator or a linter, that difference is the interesting part.

## Installing v4 and reading the first document into a struct

Installation is a single go get with the version in the path.

```bash
go get go.yaml.in/yaml/v4
```

The API documentation lives at pkg.go.dev for that path, which is a small reminder that the major version suffix is part of the import, not just a release tag.

The readme's example is the clearest illustration of the library's shape, and it does something specific: it decodes the same document twice, once into a typed struct and once into a generic map, then marshals both back. The struct part shows the field mapping rules.

```go
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
	A string
	B struct {
		RenamedC int   `yaml:"c"`
		D	[]int `yaml:",flow"`
	}
}
```

Three things to take from that. Field names must be exported, because unexported fields are invisible to the unmarshaller. A struct tag renames a field, here mapping RenamedC onto the key c. And the tag yaml:",flow" forces the slice to be emitted in flow style, which is why the round trip of the struct comes back as d: [3, 4] while the map version comes back expanded over several lines.

That difference in output between the struct and the map is the interesting part of the example. Same input, same library, two serialisers, and the map path uses map[any]any with the any key type, which is the shape you get when you have no schema at all. The readme prints the expected output for both, so you can see the flow style on one side and the block style on the other without running anything.

## The go-yaml CLI exists to show you the parsing stages

The repository ships a command line tool, and the readme explains why in a way that is unusual and useful.

The tool is described as a way to understand the internal stages and final results of YAML processing with the library, and the readme strongly encourages you to show pertinent output from it when reporting and discussing issues. That is a debugging aid aimed at contributors, and it is the sort of tool that turns a bug report from a description of symptoms into a description of a specific stage going wrong.

You can build it from the repository with make, or install it directly.

```bash
make go-yaml
./go-yaml --help
./go-yaml <<< '
foo: &a1 bar
*a1: baz
' -n        # Show value on decoded Node structs (formatted in YAML)
```

The example input is a YAML anchor and an alias, which is the kind of feature that has well known sharp corners in YAML implementations. The -n flag shows the value on the decoded Node structs, formatted back as YAML, so you can see the internal representation rather than the result of a decode into your own types.

The install route is a plain go install.

```bash
go install go.yaml.in/yaml/v4/cmd/go-yaml@latest
```

The repository layout backs up the Node-centric design. There is a node.go and a plugin.go at the top level, an internal/ directory, a plugin/ directory, a util/ directory, and a yts/ directory. The examples directory is the clearest evidence of the scope: more than twenty example programs, including load_into_node, node_programmatic_build, node_load_strict_unmarshaler, multi_document_loader, multi_document_dumper, json-comments and several dumper and loader variants, plus separate with_v4_option and with_v4_override examples.

That example set is a good map of the library's real surface, which is considerably larger than the marshal and unmarshal pair most people use.

## The makefile refuses to use your Go installation by default

The build system in this repository is worth a look even if you never contribute, because it encodes an opinion about reproducibility that is not common in Go projects.

The GNUmakefile is set up to support testing, automation and development tasks in a completely deterministic way. By default it will not use your system's Go installation, or any other system tools it needs. The only things it relies on from your system are Linux or macOS, GNU make at 3.81 or later, git, bash and curl. Everything else, including Go and the Go utilities, is installed and cached as needed under a .cache directory.

The available commands include make test, make lint tidy, make test-shell, make shell, and make distclean, which removes all generated files including .cache. The test target takes parameters: a version selector of v=1, extra CLI options passed as o, a specific Go version through GO-VERSION, and a Go path through GO_YAML_PATH.

If you want to use your own Go, you export GO_YAML_PATH to the directory holding the go binary.

```bash
export GO_YAML_PATH=$(dirname "$(command -v go)")
make <rule>
# or:
make <rule> GO_YAML_PATH=$(dirname "$(command -v go)")
```

The readme notes that GO-VERSION and GO_YAML_PATH are mutually exclusive, and that when GO_YAML_PATH is set the Makefile uses your own Go installation and ignores any GO-VERSION setting. There is also a make shell target that starts a subshell with the same environment the makefile set up for its commands, which is the practical way to work inside that environment.

The trade-off is real. A contributor needs curl, a Makefile and a shell to get a working Go toolchain, with a download on first run, in exchange for a build that does not vary with whatever happens to be on the machine. For a library whose test matrix spans several Go versions, that consistency is the point.

## Where this fork is the wrong choice

Three limitations are worth being clear about.

The first is the version churn in the import path. Moving to v4 means changing the import in every file that uses the package, and a project that mixes dependencies still on gopkg.in/yaml.v3 or on the old github.com/go-yaml/yaml path will have two YAML libraries in the same binary. That is not fatal, since the module paths differ, but it doubles the surface and it means two implementations of the same spec with possibly different behaviour on the 1.1 edge cases described above.

The second is the licence, and it is more permissive than the label suggests at first glance. The repository is recorded as Apache-2.0, and the readme says the yaml package is licensed under the MIT and Apache License 2.0 licenses with details in the LICENSE file. So it is a dual licence offering, which is generous, and there is also a NOTICE file in the repository root. A dual MIT and Apache 2.0 grant gives you the option of whichever terms suit you, with the Apache patent grant and the NOTICE attribution requirement as the more formal of the two. Read the LICENSE file for the detail rather than relying on this summary.

The third is that the fork is a bet on the consolidation working. The readme states the goal of earning the trust of the go-yaml forks to switch back to this repository as their upstream. If several downstream projects do migrate, this becomes the default and the risk of choosing it shrinks. If they do not, you are maintaining a dependency whose community is smaller than the one the old import path had, and the v1 to v3 lines you left behind are frozen anyway.

On the alternative, the honest comparison is with a YAML library that implements 1.2 cleanly and refuses the 1.1 legacy, or with a config-specific library that gives you schema validation and typed decoding on top. The difference in approach is the same one the compatibility section describes. This library keeps 1.1 behaviour deliberately so that existing configuration files keep parsing, and it accepts that a document's meaning can depend on the Go type you decode into. A stricter library would make those files fail loudly instead, which is better for a new project with a known schema and worse for a tool that has to read whatever its users wrote.

The version history gives one more piece of information. The repository has no GitHub releases, so the tags in the repository are the release mechanism, and the module path is what you should be pinning your dependency to.

## Conclusion

This fork suits a Go team that wants a YAML library maintained by the people who write the specification, and it does not suit an application that depends on YAML 1.1 behaviours such as yes and no booleans or 0777 octals, since v4 keeps some of those only for backwards compatibility. Before you migrate, check which of your YAML files rely on the 1.1 boolean or octal forms, and decide the import path now, because the module path changes to go.yaml.in/yaml/v4 and a project that picks the wrong one ends up with a dependency nobody intends to maintain.

## FAQ

### What is go.yaml.in/yaml and why was it forked from go-yaml?

It is the YAML organisation's maintained fork of the go-yaml library. The YAML team took over after go-yaml's author labelled that repository unmaintained in April 2025, and the fork now holds a team of maintainers including representatives of major downstream projects.

### Which version of go.yaml.in/yaml should a new project use?

Use v4, with the import path go.yaml.in/yaml/v4. Versions v1, v2 and v3 are frozen legacy and receive security fixes only, with all new features and routine bug fixes happening in v4.

### How do I install the go.yaml.in/yaml v4 package?

Run go get go.yaml.in/yaml/v4. The API documentation for that path is at pkg.go.dev/go.yaml.in/yaml/v4.

### Does go.yaml.in/yaml support YAML 1.2 booleans and octals?

It supports most of YAML 1.2 but keeps some 1.1 behaviour. yes, no, on and off are treated as booleans when decoded into a typed bool, and octals are encoded and decoded as 0777 per YAML 1.1 as well as in the 0o777 form, while base-60 floats are not supported.

### What licence is the go.yaml.in/yaml package under?

The readme says the yaml package is licensed under the MIT and Apache License 2.0 licenses, with the details in the LICENSE file in the repository root.

## Sources

- [Issues](https://github.com/yaml/go-yaml/issues)
- [License: Apache-2.0](https://github.com/yaml/go-yaml/blob/main/LICENSE)
- [README](https://github.com/yaml/go-yaml/blob/main/README.md)
- [yaml/go-yaml on GitHub](https://github.com/yaml/go-yaml)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/yaml-go-yaml
