js-yaml
js-yaml is the YAML parser and writer most of the JavaScript ecosystem sits on. You call load() to turn a YAML string into plain objects and dump() to go the other way, which covers config files, CI definitions, and frontmatter. Version 5, released in June 2026, is a full rewrite: it targets YAML 1.2 by default, passes the entire official YAML test suite, and replaced the old schema and custom-tag APIs with a new design. It ships a single dependency (argparse, for its small CLI) plus browser builds and bundled TypeScript types.
Still the default YAML choice for JavaScript, and v5 makes it spec-complete while staying actively maintained across three release lines. Budget real time for the v4 to v5 migration if you use custom tags, and reach for the yaml package when you must round-trip comments.
Use it if
- You need to read or write YAML config in Node and want the library that linters, build tools, and frontmatter packages already depend on, so it is probably in your node_modules anyway
- You care about spec correctness: v5 handles both YAML 1.2 and 1.1 and passes the official YAML test suite
- You parse YAML from users or third parties and want built-in guards: maxAliases, maxDepth, and maxTotalMergeKeys cap alias bombs, deep nesting, and merge-key blowups
- You want a small API surface: load, loadAll, dump, and a schema option cover almost every job
- You need to edit YAML while preserving comments and formatting; js-yaml parses to plain objects and throws that information away, while the yaml package keeps the full document model for round-tripping
- Your project is built on v4 idioms: v5 removed DEFAULT_SCHEMA, the Type class, and dump options like replacer and styles, so upgrading code with custom tags is a real migration, not a version bump
- You depend on YAML 1.1 behavior by default: in v5 merge keys (<<) are off unless you add mergeTag, and !!timestamp only becomes a Date under YAML11_SCHEMA, so timestamps in a plain load() now come back as strings
- You are bundle-size sensitive on the front end: about 17 KB gzipped is fine on a server but heavy in a browser if all you parse is one small config blob
Setup reality
npm install js-yaml and you can parse immediately; no config, one dependency. The v5 surprises: there is no ESM default export, so import yaml from 'js-yaml' breaks and you need named or namespace imports; load('') now throws instead of returning undefined; and types are bundled, so drop @types/js-yaml if you had it. Teams coming from v4 with custom Type classes or dump styles have a genuine porting job because the tag model changed, not just the names. The migration guide is thorough and worth reading before you touch anything.
Patterns
Parse a YAML file into an objectload-yaml-file
import { load } from 'js-yaml'
import { readFileSync } from 'node:fs'
const config = load(readFileSync('config.yml', 'utf8'))load() throws YAMLException on bad input, and since v5 an empty file throws too instead of returning undefined.
Parse a multi-document streamload-multi-document
import { loadAll } from 'js-yaml'
const docs = loadAll(source) // one array entry per --- documentload() throws on sources with --- separators; loadAll is the only way to read them, and it returns [] for empty input.
Serialize an object to YAMLdump-object-to-yaml
import { dump } from 'js-yaml'
const text = dump(
{ name: 'app', ports: [80, 443] },
{ indent: 2, sortKeys: true }
)Default lineWidth is 80, so long strings get folded across lines; pass lineWidth: -1 to keep them intact.
Re-enable YAML merge keys (<<)enable-merge-keys
import { load, CORE_SCHEMA, mergeTag } from 'js-yaml'
const doc = load(source, {
schema: CORE_SCHEMA.withTags(mergeTag)
})The v5 default CORE_SCHEMA drops merge keys; without mergeTag a << line lands in your object as a literal '<<' property instead of merging the anchor.
Parse legacy YAML 1.1 documentsyaml-1-1-compat
import { load, YAML11_SCHEMA } from 'js-yaml'
const doc = load(source, { schema: YAML11_SCHEMA })
// !!timestamp -> Date, !!binary -> Uint8Array, !!set -> SetUnder the default 1.2 schema, timestamps stay strings and yes/no stay strings. YAML11_SCHEMA restores v4-style typing, but !!binary now yields Uint8Array, not Buffer.
Parse untrusted YAML with limitslimit-untrusted-input
import { load } from 'js-yaml'
const doc = load(userInput, {
maxAliases: 0, // reject *ref aliases entirely
maxDepth: 20 // default is 100
})Alias expansion is how billion-laughs YAML bombs work; maxAliases: 0 rejects aliases outright and maxDepth caps collection nesting.
Control duplicate key behaviorduplicate-keys-json-mode
import { load } from 'js-yaml'
load('a: 1\na: 2') // throws: duplicate key
load('a: 1\na: 2', { json: true }) // { a: 2 }Duplicate mapping keys throw by default; json: true switches to JSON.parse behavior where the last value wins.
Define a custom !tagcustom-scalar-tag
import { load, dump, CORE_SCHEMA, defineScalarTag } from 'js-yaml'
const regexpTag = defineScalarTag('!regexp', {
resolve: (source) => new RegExp(source),
identify: (data) => data instanceof RegExp,
represent: (data) => data.source
})
const schema = CORE_SCHEMA.withTags(regexpTag)
load('pattern: !regexp ^a+$', { schema })The v4 Type class is gone; tags are built with defineScalarTag, defineSequenceTag, or defineMappingTag and registered via schema.withTags(). instanceOf became identify.
Use from CommonJScommonjs-usage
const { load, dump } = require('js-yaml')
const doc = load('a: 1')v5 has no default export by design: destructured require works, and ESM code should use named imports or import * as yaml, never import yaml from 'js-yaml'.
Get readable errors with file contexterror-with-filename
import { load, YAMLException } from 'js-yaml'
try {
load(source, { filename: 'deploy.yml' })
} catch (e) {
if (e instanceof YAMLException) console.error(e.message)
else throw e
}The filename option puts the path into error messages next to line and column, which matters when you parse many files in one run.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| yaml | npm | You need comment and formatting preservation or programmatic document editing; it exposes the full document model instead of plain objects. |
| gray-matter | npm | You only need to pull YAML frontmatter out of Markdown files; it wraps the parsing for you. |