Library / SDK
eemeli/yaml avatar
eemeli/yaml

yaml keeps comments through a three-layer API and accepts any string

YAML parser and serialiser for JavaScript

1,698 stars185 forksTypeScriptISC

At a glance

What is it?
The yaml library for JavaScript parses without throwing on any input, round trips comments and blank lines, and exposes Parse and Stringify, Documents, and the Lexer, Parser and Composer beneath them. The catch is that v3 is a prerelease and the documented install command is the next tag, not latest.
Who is it for?
Adopt yaml when you edit YAML as text and cannot lose comments, blank lines or key order, or when you parse configuration from input that is not guaranteed to be valid, because the parse functions accept any string and return what they can rather than throwing. Do not adopt it if you need a small footprint in a bundled browser app without checking the bundle, and do not pin to the 3.x prerelease until the APIs you use are documented there.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 30 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Never throws on bad input is a design choice, not a safety net

The README states that the library can accept any string as input without throwing, parsing as much YAML out of it as it can.

That single behaviour changes how you use it. The simplest `parse()` API accepts a string without throwing. If you need document-level diagnostics, use the Documents layer, where `#errors` and `#warnings` are exposed per document.

So the practical rule is: if you are parsing configuration you control, the simple API may be enough. If you need diagnostics, use `parseDocument` or `parseAllDocuments` and inspect `#errors` and `#warnings`.

The other headline claims are similarly specific. Both YAML 1.1 and YAML 1.2 are supported along with all common data schemas, the library passes all of the yaml-test-suite tests, and it can parse, modify and write YAML comments and blank lines.

That last one is the reason to choose this library over a plain JSON-style parser. Comment preservation is not a nice to have when the file is a Kubernetes manifest, a GitHub Actions workflow or a config file that humans argue about in review. Round tripping such a file through a parser that discards comments produces a diff that touches every line.

The library has no external dependencies and runs on Node.js as well as in modern browsers, which makes it viable in a frontend bundle as well as a build script.

parse and stringify deliberately mirror the JSON built-ins

The simplest layer is designed to be a drop-in mental replacement for the JSON pair:

- `parse(str, reviver?, options?): value` - `stringify(value, replacer?, options?): string`

The optional second and third arguments mirror the JSON signatures, so if you already pass a reviver or a replacer to `JSON.parse` you can pass the same shape here.

The flow style example from the README shows the round trip:

js
import { parse, stringify } from 'yaml'

parse('[ true, false, maybe, null, 3.14159 ]')
// [ true, false, 'maybe', null, 3.14159 ]

stringify([true, false, 'maybe', null, 3.14159])
// `- true
// - false
// - maybe
// - null
// - 3.14159
// `

Note the third element. `maybe` is not a YAML keyword, so it parses as the string `'maybe'`, and it is written back without quotes because block context makes the plain scalar unambiguous. This is the library being conservative about requoting rather than noisy.

The mapping example shows the block scalar choice, which matters when you write files:

js
stringify({ number: 3, plain: 'string', block: 'two\nlines\n' })
// `number: 3
// plain: string
// block: |
//   two
//   lines
// `

A string with trailing newlines becomes a literal block scalar rather than an escaped one, because that is the readable form. `stringify` also writes the trailing newline, which is why the expected output ends with an empty line.

Reading a file is ordinary Node, with the parser as the only addition:

js
import fs from 'node:fs'
import { parse } from 'yaml'

const file = fs.readFileSync('./file.yml', 'utf8')
parse(file)

Because `parse` takes a string, it works equally on a path you read, a string you fetch, or a value from a form field, with no options required for any of those.

Documents is where comments, errors and warnings live

The second layer gives you the Document object, which is the representation to use whenever the file itself matters rather than just its value.

The exposed surface is small and deliberate. A `Document` has `#value` for the parsed content, `#directives` for stream level directives such as the version, and two diagnostic properties, `#errors` and `#warnings`. Construction takes the same optional replacer and options arguments as `stringify`. Two functions produce documents: `parseDocument(str, options?)` returns one, and `parseAllDocuments(str, options?)` returns an array, which is what a multi-document YAML stream needs.

Because documents retain comments and blank lines, editing is done on the document and written back with `toString()`. The AST underneath is composed of a small node vocabulary: `Scalar`, `YAMLMap`, `YAMLSeq`, plus factory methods on the document itself, `createAlias`, `createNode` and `createPair`.

Traversal is done with two functions:

js
import {
  Scalar,
  visit,
  visitAsync,
  YAMLMap,
  YAMLSeq
} from 'yaml'

`visit` is synchronous and `visitAsync` is the counterpart for cases where you need to await inside the callback. They walk a document's nodes, which is how you would rewrite every value matching a condition, migrate a deprecated key, or apply a naming convention without going through the plain value and losing the comments.

The practical dividing line between the two layers is this. If you read configuration and hand the result to application code, `parse` is enough. If you read a file, change something, and write it back for humans to review, you need a `Document`, because the value alone cannot carry the comments you must not lose.

Lexer, Parser and Composer exist for tooling, not for applications

The third layer is the parser itself, split into three stages you can drive directly:

- `new Lexer().lex(src)` - `new Parser(onNewLine?).parse(src)` - `new Composer(options?).compose(tokens)`

They are imported from the package root alongside `Composer`, `Lexer` and `Parser`. Running them separately means you can tokenize YAML without building a document, which is what an editor integration, a linter or a syntax highlighter needs. The `onNewLine` callback on the Parser is the mechanism for tracking positions, which is exactly the information a tool needs and an application does not.

The README is candid that this layer exists for a narrower audience, saying it lets you get progressively closer to YAML source. That is also where you take on responsibility: the Document layer handles errors and warnings for you, the raw token layer does not.

There is also a command-line tool, registered in the package metadata as the bin entry pointing at `./bin.js`. The package exports two subpaths: the root export for the main API, and a `./util` export that resolves to `dist/util.js` with its own type declarations. That util subpath is not described in the README's API overview, so if you are reaching for it, read its declarations at the version you pin rather than assuming stability.

The build is `rolldown -c`, which is worth noting if you bundle: this package is distributed as `dist/` only, since the `files` field in the package metadata lists `dist/` and nothing else.

Install, versions and the TypeScript floor

The install commands are short, and the first one is the one that matters:

sh
npm install --save-exact yaml@next

The `--save-exact` flag pins the version with no range, and the tag is `next`, not `latest`. That combination tells you the README is written from the perspective of tracking the newest published version. If you want the current stable release, you install `yaml` without the tag and accept whatever `latest` resolves to.

For Deno, the equivalent is:

sh
deno add jsr:@eemeli/yaml

The module is ESM. The package metadata sets `"type": "module"` and the root export resolves to `./dist/index.js` with `./dist/index.d.ts` for types. There is no CommonJS entry listed, which matters for older toolchains that still expect `require()` to work.

The TypeScript requirement is specific and stated twice in effect. The minimum supported TypeScript version of the included typings is 5.9, and for earlier versions you may need to set `skipLibCheck: true`. The README also warns that this requirement may be raised between minor versions of the library, which is unusual candour and means your build can break on a dependency bump even though your code did not change.

On versioning policy, the README is equally explicit: any change that breaks a documented endpoint or API is a semver-major change, while undocumented library internals may change between minor versions, and previous APIs may be deprecated but not removed. That is a well-behaved policy and it means you can rely on the documented surface.

The version currently in the metadata is 3.0.0-2, a prerelease, with v2.9.1 released on 2026-09-11 and v2.9.0 on 2026-05-11. The last push was on 2026-09-23.

Where it does not fit, and what to compare against

Two honest limits.

First, this is a general purpose YAML library with YAML 1.1 and 1.2 both enabled and all common data schemas supported. That breadth is useful for a general tool, but a project that needs a strict subset should review the available schema options rather than assuming them.

Second, if all you need is to read a config file and never write it back, the comment preservation, the Document layer and the AST buy you nothing, and any smaller parser would do. The value appears the moment a human edits the file in the same repository.

The comparison with other YAML libraries is best framed in terms of requirements. This library can parse, modify and write YAML comments and blank lines, and its Document layer exposes the parsed document alongside diagnostics. That retention model also explains the three-layer API: the simple pair is available when the plain value is sufficient.

If you need separate schema validation, you can layer that onto the parsed document. The `Documents` layer exposes `#errors` and `#warnings` for diagnostics, while validation remains outside the documented parse-and-stringify API shown here.

Licence is ISC, which is permissive with no copyleft obligation, and the repository is not archived. Build and test tooling is vitest with a `test:all` target that also type checks the distribution, which is the check that would catch a packaging problem for consumers.

Editorial conclusion

Adopt yaml when you edit YAML as text and cannot lose comments, blank lines or key order, or when you parse configuration from input that is not guaranteed to be valid, because the parse functions accept any string and return what they can rather than throwing. Do not adopt it if you need a small footprint in a bundled browser app without checking the bundle, and do not pin to the 3.x prerelease until the APIs you use are documented there. Verify first which install tag you actually want, since the README's install command uses yaml@next rather than latest, and the minimum supported TypeScript version for the included typings is 5.9.

Frequently asked questions

How do I install the yaml package for JavaScript?

The README gives npm install --save-exact yaml@next for npm and deno add jsr:@eemeli/yaml for Deno. Note that the tag is next rather than latest, and the package metadata lists version 3.0.0-2 as a prerelease alongside v2.9.1.

Does the yaml library throw on invalid input?

No. The README states it can accept any string as input without throwing, parsing as much YAML out of it as it can. To detect problems, use parseDocument or parseAllDocuments and check the document's #errors and #warnings.

Does the yaml library preserve comments and formatting?

Yes. The README states the library supports parsing, modifying and writing YAML comments and blank lines. This works through the Document layer, since a plain value cannot carry comments.

What TypeScript version do the yaml typings need?

The minimum supported TypeScript version of the included typings is 5.9, and earlier versions may require setting skipLibCheck: true. The README notes this requirement may be updated between minor versions of the library.

Can I use yaml to modify individual nodes in a document?

Yes. Use the Document and node layer with Scalar, YAMLMap, YAMLSeq and the doc.createNode, doc.createPair and doc.createAlias methods, then traverse with visit or visitAsync. The lowest layer exposes Lexer, Parser and Composer for token level access.

Official sources

  1. eemeli/yaml on GitHub
  2. License: ISC
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/eemeli-yaml.svg)](https://hysenlabs.com/projects/eemeli-yaml)

Was this useful?