The go-yaml v4 plugin system lets applications replace selected processing stages without adding optional dependencies to the core module.
A plugin API defines a role such as yaml-parser,
loader-limits, tab-indent, or error-format.
Each API can have several named implementations.
The current implementations are:
| API | Implementation | Package | Default |
|---|---|---|---|
yaml-parser |
go-yaml |
core | yes |
yaml-parser |
reference |
plugin/yamlparser/reference |
no |
error-format |
error-format |
plugin/errorformat/v3 and plugin/errorformat/v4 |
yes |
loader-limits |
loader-limits |
plugin/loaderlimits |
yes |
tab-indent |
tab-indent |
plugin/tabindent |
yes |
The reference YAML-parser package is a separate Go module.
Importing the core go.yaml.in/yaml/v4 module does not acquire its generated
code or dependencies.
Pass an implementation to yaml.WithPlugin when code constructs the plugin
directly.
import (
"go.yaml.in/yaml/v4"
"go.yaml.in/yaml/v4/plugin/loaderlimits"
"go.yaml.in/yaml/v4/plugin/tabindent"
)
var value any
err := yaml.Load(input, &value,
yaml.WithPlugin(loaderlimits.New(loaderlimits.DepthValue(50))),
yaml.WithPlugin(tabindent.New()))The public plugin interfaces are:
type ErrorFormatPlugin interface {
FormatLoadError(err *LoadError) string
FormatDumpError(err *DumpError) string
}
type ParserPlugin interface {
Parse(input []byte) ([]PluginEvent, error)
}
type LoaderLimitsPlugin interface {
CheckDepth(depth int, ctx *DepthContext) error
CheckAlias(aliasCount, constructCount int) error
}
type TabIndentPlugin interface {
TabIndentConfig() IndentConfig
}The built-in go-yaml parser remains the default unless a YAML-parser plugin is
selected.
PluginEvent represents stream and document boundaries, mappings, sequences,
scalars, and aliases with ordinary Go fields.
The loader validates a YAML-parser plugin's complete event stream before
composing nodes.
Depth limits apply to that stream and cannot limit work already performed by
an external parser.
Input is buffered when a YAML-parser plugin is active. Plugin implementations must support independent concurrent calls.
See Error-format v3 and Error-format v4 for the formatter guides.
Bare yaml.Load, yaml.Dump, yaml.NewLoader, and yaml.NewDumper use
v4 defaults:
- default safety limits equivalent to
loaderlimits.New() - v4 error formatting equivalent to
errorformatv4.Must()
Version presets select error formatting as follows:
| Preset | Error Formatter |
|---|---|
yaml.WithV2Defaults() |
v4 formatter |
yaml.WithV3Defaults() |
v3 formatter |
yaml.WithV4Defaults() |
v4 formatter |
Historical go-yaml v2 errors used the yaml: ... prefix, with line N: when
position information was available.
WithV2Defaults() selects v2-compatible processing and output options together
with the current v4 error formatter.
It does not reproduce historical v2 error strings.
For legacy-style error strings, apply the v3 formatter after the preset:
opts := yaml.Options(
yaml.WithV2Defaults(),
yaml.WithPlugin(errorformatv3.New()),
)The legacy APIs select their error formatters as follows:
| API | Error Formatter |
|---|---|
yaml.Unmarshal, yaml.NewDecoder |
v3 formatter |
yaml.Marshal |
v3 formatter |
yaml.NewEncoder |
v4 formatter |
NewEncoder keeps v3-compatible serialization options and explicitly selects
v4 error formatting.
Serialization options and error formatting are independent settings.
Plugins can be configured from YAML strings using yaml.OptsYAML:
plugin:
loader-limits:
depth: 50
alias: 1000
error-format:
v4:
position: longopts, err := yaml.OptsYAML(configYAML)
if err != nil {
return err
}
err = yaml.Load(data, &out, opts)See the plugin-specific guides for supported configuration keys. An unversioned scalar first selects a registered implementation. Otherwise, it can name an empty configuration entry accepted by the default implementation. For example, these configurations are equivalent:
plugin:
error-format: v3plugin:
error-format:
v3: {}This shorthand works for any plugin whose default factory accepts that entry.
Registered implementation names take precedence.
Explicit name fields and versioned selectors always select implementations.
The same shorthand works with --plugin=error-format=v3 at runtime and
make cli PLUGIN=error-format=v3 at build time.
The default implementation is also named error-format:
opts := yaml.WithNamedPlugin("error-format")plugin:
error-format: error-formatThe loader-limits plugin controls maximum nesting depth and alias expansion. The built-in defaults remain active unless another loader-limits implementation is selected.
loader := yaml.NewLoader(data, yaml.WithPlugin(loaderlimits.New()))
loader = yaml.NewLoader(data,
yaml.WithPlugin(loaderlimits.New(loaderlimits.AliasNone())))
loader = yaml.NewLoader(data,
yaml.WithPlugin(loaderlimits.New(loaderlimits.DepthValue(50))))| Option | Effect |
|---|---|
DepthValue(n) |
Set the flow and block nesting limit |
DepthNone() |
Disable depth checking |
DepthFunc(fn) |
Supply a depth policy function |
AliasValue(n) |
Set the alias expansion limit |
AliasNone() |
Disable alias checking |
AliasFunc(fn) |
Supply an alias policy function |
The tab-indent plugin enables tab-aware structural indentation while loading
and can emit structural indentation with tabs while dumping.
Its default auto mode detects tabs or spaces for each document and emits tabs.
plugin := tabindent.New()
err := yaml.Load(input, &value, yaml.WithPlugin(plugin))
output, err := yaml.Dump(value, yaml.WithPlugin(plugin))Configuration accepts mode (auto or tabs), load (auto, spaces, or
tabs), dump (spaces or tabs), and auto (document or stream).
External packages register named implementations for yaml.OptsYAML and
yaml.WithNamedPlugin.
Call each optional package's Register function once during startup.
if err := reference.Register(); err != nil {
log.Fatal(err)
}
if err := tabindent.Register(); err != nil {
log.Fatal(err)
}Configuration can use mappings, strings, or booleans:
plugin:
yaml-parser: reference@v0.2.6
loader-limits:
depth: 50
alias: 1000
tab-indent:
mode: auto
auto: documentA string is the short form IMPLEMENTATION or IMPLEMENTATION@VERSION.
Versions with and without the leading v are accepted.
Documentation and Git tags use the v prefix.
true selects the API's default implementation with its defaults.
false leaves the plugin disabled.
A mapping can contain name, version, disable, and implementation
settings.
disable: true has the same effect as false, even when saved settings remain
in the mapping.
Null is invalid.
The host removes name, version, and disable before calling the
implementation factory.
A requested version must match the linked implementation exactly.
Disabling loader-limits leaves the core safety limits active.
The loader-limits implementation accepts integer depth and alias settings.
A null setting disables that one check.
Use loader-limits and LoaderLimitsPlugin in new code.
For migration from earlier v4 release candidates, the limit configuration
key and implementation name are accepted as deprecated aliases.
Configuring both limit and loader-limits is an error.
The deprecated LimitPlugin type alias also remains available.
The previously published go.yaml.in/yaml/v4/plugin/limit import path is
retained as a module deprecation marker.
Its original implementation remains available to users pinned to an earlier
release candidate.
Migrate imports to go.yaml.in/yaml/v4/plugin/loaderlimits.
The former parser name is not an alias for yaml-parser.
go.yaml.in/yaml/v4/plugin/yamlparser/reference adapts the generated Go parser
from github.com/yamlstar/yamlstar-plugin-parser-reference.
The canonical parser source remains in yaml/yaml-reference-parser-clj.
It requires Go 1.24 or newer and does not need Clojure, Gloat, CGO, or a shared
library at runtime.
import reference "go.yaml.in/yaml/v4/plugin/yamlparser/reference"
var value any
err := yaml.Load(input, &value, yaml.WithPlugin(reference.New()))The ordinary go-yaml binary contains only core implementations.
Optional implementations must be linked when the CLI is built.
CONFIG is a YAML options file.
It selects build dependencies and embeds the full configuration as the binary's
defaults.
make cli CONFIG=options.yamlPLUGIN is only the plugin selector DSL.
It never names a file and never contains YAML.
make cli PLUGIN=yaml-parser=reference@v0.2.6,loader-limits
make cli PLUGIN=error-formatSelectors have these forms:
API
API@VERSION
API=IMPLEMENTATION
API=IMPLEMENTATION@VERSION
A bare API selects its default implementation.
For optional implementations, the build resolves the newest v-prefixed
release when no version is supplied.
A version selects that exact release.
Go downloads released modules through its configured module proxy and stores
them in the Go module cache.
Build staging and generated workspaces live under .cache/.
For local development, place the related checkouts under repos/ and set the
matching override:
make cli PLUGIN=yaml-parser=reference@v0.2.6,loader-limits \
REFERENCE-PARSER-LOCAL=1The compiled command uses the same selector DSL at runtime:
./go-yaml --plugin=yaml-parser=reference@0.2.6,loader-limits -j data.yamlA runtime selector can choose only implementations already linked into the
binary.
It cannot download or add Go code.
-C FILE and --config=FILE continue to load YAML configuration files.
A runtime configuration replaces embedded defaults.
JSON, YAML, node, and event modes support YAML-parser plugins. Token and legacy loading modes reject external parsers because those modes do not consume YAML-parser plugin events.
Run the optional module checks with:
make test-limit
make test-reference-parserPLUGIN-PACKAGE links released Go packages into a configured CLI without
adding adapters or dependencies to this repository.
Each package must export Register() error and register its implementations
through yaml.RegisterPlugin.
A parser implementation supplies the existing yaml.ParserPlugin interface.
make cli \
PLUGIN-PACKAGE=example.org/custom-parser/goyaml@v1.2.3 \
PLUGIN=yaml-parser=custom
./go-yaml --plugin=yaml-parser=custom -j data.yamlUse a comma-separated list to link multiple packages:
make cli \
PLUGIN-PACKAGE=example.org/parser/go@v1.2.3,example.org/limits@v0.2.0Each entry requires an explicit version such as v1.2.3 or v1.2.3-rc.1.
Local paths, unversioned entries, and @latest are not supported.
Go resolves dependencies using its configured module proxy and module cache.
The core module remains replaced by the current checkout in the staged CLI.
Packages are registered in list order before configuration is evaluated. Registration failures, duplicate registrations, and invalid plugin options fail the build before replacing an existing executable. The completed executable validates the selected implementation and version.
PLUGIN-PACKAGE links code; PLUGIN and CONFIG select implementations and
embed defaults.
A build with only PLUGIN-PACKAGE keeps ordinary defaults and makes the
registered implementations available through runtime configuration or flags.
External packages may implement any existing plugin interface.
Only one parser implementation can be selected at a time.
Runtime flags cannot download or load additional packages.
Run ./cmd/go-yaml/demo/demo-plugins.sh from a fresh clone to demonstrate the
plugins.
It builds the CLI once with the reference parser and external adapters fetched
through Go modules; no repos/* checkouts are needed.
The demo build selects the reference parser by default.
Run make cli afterward to restore the ordinary native CLI.
./cmd/go-yaml/demo/demo-plugins.sh --list
./cmd/go-yaml/demo/demo-plugins.sh 3 5-7 9
./cmd/go-yaml/demo/demo-plugins.sh -i 10-See the demo README for interactive controls, dependencies, and executable overrides.