js-yaml, yaml, and yamljs are the three primary libraries for handling YAML data in JavaScript environments. js-yaml is a mature, feature-rich parser and dumper known for its strict adherence to the YAML 1.2 specification and extensive safety features. yaml is a modern, tree-based library that prioritizes developer experience, offering robust round-trip preservation of comments and formatting while supporting both YAML 1.1 and 1.2. yamljs is a legacy port of the Python PyYAML library that is no longer actively maintained and lacks support for modern JavaScript module systems and recent YAML spec features.
YAML is a human-readable data serialization standard widely used for configuration files, data exchange, and infrastructure as code. In the JavaScript ecosystem, three main libraries have emerged to handle this format: js-yaml, yaml, and yamljs. While they all parse and stringify YAML, their underlying architectures, feature sets, and maintenance statuses differ significantly. Let's break down how they handle real-world engineering challenges.
Security is paramount when parsing untrusted YAML, as malicious payloads can execute arbitrary code or overwrite object prototypes.
js-yaml provides explicit safety levels. You must choose between load (unsafe, allows custom tags) and loadAll with the SAFE_SCHEMA option to prevent dangerous constructs.
import * as yaml from 'js-yaml';
// Safe loading: rejects custom tags and JS functions
const data = yaml.load(inputString, { schema: yaml.DEFAULT_SAFE_SCHEMA });
// Unsafe loading: allows custom JS types (risky with untrusted input)
// const data = yaml.load(inputString);
yaml is secure by default. It does not execute arbitrary code or resolve custom tags unless explicitly configured to do so. Its API design discourages unsafe patterns.
import { parse } from 'yaml';
// Always safe by default; no custom tags resolved unless specified
const data = parse(inputString);
// To allow custom tags, you must explicitly define a schema
// const doc = new YAML.Document({ customTags: ... });
yamljs lacks modern safety features and clear separation between safe and unsafe modes. It relies on older patterns that may expose applications to prototype pollution if not carefully wrapped.
import YAML from 'yamljs';
// No explicit safe/unsafe schema option in the API
// Relies on internal implementation details that are less transparent
const data = YAML.parse(inputString);
When building tools that edit configuration files, preserving existing comments and indentation is often a requirement. Losing this metadata can frustrate users and destroy documentation embedded in config files.
js-yaml treats YAML as pure data. When you parse and then stringify, all comments and original formatting are lost. It outputs a canonical, clean version of the data.
import * as yaml from 'js-yaml';
const doc = yaml.load('# Comment here\nname: John');
const output = yaml.dump(doc);
// Output: "name: John\n" (Comment is gone)
yaml uses a tree-based representation (CST/AST) that retains comments, whitespace, and node details. You can modify a specific value and write the file back with comments intact.
import { parse, stringify, Document } from 'yaml';
const doc = new Document(parse('# Comment here\nname: John'));
doc.setIn(['name'], 'Jane');
const output = stringify(doc);
// Output: "# Comment here\nname: Jane\n" (Comment preserved)
yamljs behaves like js-yaml in this regard. It focuses on data extraction and does not preserve comments or formatting nuances during serialization.
import YAML from 'yamljs';
const data = YAML.parse('# Comment here\nname: John');
const output = YAML.stringify(data);
// Output: "name: John\n" (Comment is lost)
The YAML specification has evolved. YAML 1.2 clarified many ambiguities from 1.1, particularly regarding string quoting and boolean recognition.
js-yaml strictly follows YAML 1.2. It will not parse certain YAML 1.1 constructs (like unquoted yes/no as booleans) unless explicitly configured to use a legacy schema, ensuring predictable behavior.
import * as yaml from 'js-yaml';
// YAML 1.2: 'yes' is a string, not a boolean
const data = yaml.load('enable: yes');
console.log(data.enable); // Outputs: "yes" (string)
yaml supports both YAML 1.1 and 1.2. You can configure the version per document, making it flexible for legacy projects while defaulting to modern standards.
import { parse } from 'yaml';
// Default is 1.2, but you can specify 1.1
const data = parse('enable: yes', { version: '1.1' });
console.log(data.enable); // Outputs: true (boolean in 1.1)
yamljs is based on an older understanding of the spec and often behaves inconsistently with modern expectations. It does not offer clear version switching, leading to potential parsing surprises.
import YAML from 'yamljs';
// Behavior may vary; often treats 'yes' as boolean due to legacy 1.1 influence
const data = YAML.parse('enable: yes');
console.log(data.enable); // Likely outputs: true (inconsistent)
The way these libraries model YAML data affects how you interact with them.
js-yaml uses a functional, data-centric approach. You load a string into a plain JavaScript object and dump an object back to a string. It is simple and effective for stateless operations.
import * as yaml from 'js-yaml';
// Functional style: String -> Object -> String
const obj = yaml.load(fileContent);
obj.version = 2;
const newContent = yaml.dump(obj);
yaml uses a document-centric, object-oriented approach. It creates a Document object that represents the entire YAML structure, allowing granular manipulation of nodes before serialization.
import { Document } from 'yaml';
// OO style: Create Document -> Modify Nodes -> Serialize
const doc = new Document();
doc.contents = { name: 'Alice' };
doc.get(['name']).comment = 'User name';
const newContent = doc.toString();
yamljs uses a class-based wrapper around a parser that feels dated compared to modern ES6+ patterns. It often requires callback-style asynchronous loading for files, which clashes with modern async/await flows unless wrapped.
import YAML from 'yamljs';
// Callback-based file loading (legacy pattern)
YAML.load('config.yaml', function(result) {
console.log(result);
});
// Or synchronous blocking call
// const result = YAML.load('config.yaml');
yamljs has not seen significant updates in years. Its repository shows minimal activity, and it lacks support for modern features like ES modules without transpilation hacks. It does not handle complex YAML 1.2 features reliably.
Recommendation: If you are starting a new project, do not use yamljs. It poses a maintenance risk and offers no advantages over the other two options. For existing projects using yamljs, migrating to js-yaml (for simple data) or yaml (for tooling) should be a priority.
// ❌ Avoid this in new code
import YAML from 'yamljs';
// ✅ Prefer this instead
import { parse } from 'yaml';
// OR
import * as yaml from 'js-yaml';
You need to validate GitHub Actions or Kubernetes manifests in a pipeline. Security and strict spec compliance are key.
js-yamlimport * as yaml from 'js-yaml';
try {
const config = yaml.load(ciFile, { schema: yaml.DEFAULT_SAFE_SCHEMA });
validateStructure(config);
} catch (e) {
console.error('Invalid CI config', e);
}
You are building a UI that lets users edit a docker-compose.yml file. You must keep their comments and formatting.
yamlyaml preserves the comments and structure when saving changes back to disk.import { parse, stringify, Document } from 'yaml';
const doc = new Document(parse(fileContent));
doc.setIn(['services', 'web', 'image'], 'nginx:latest');
fs.writeFileSync('docker-compose.yml', stringify(doc));
You found an old build script using yamljs that loads a config file synchronously.
js-yaml or yaml.yamljs uses callbacks and lacks modern safety. A quick swap improves reliability.// Old
// const config = YAML.load('config.yaml');
// New
import * as yaml from 'js-yaml';
const config = yaml.load(fs.readFileSync('config.yaml', 'utf8'));
| Feature | js-yaml | yaml | yamljs |
|---|---|---|---|
| Primary Use | Data parsing, validation | Tooling, editing, round-trip | Legacy systems |
| Comments | ❌ Lost on dump | ✅ Preserved | ❌ Lost on dump |
| Safety | ✅ Explicit safe/unsafe modes | ✅ Secure by default | ⚠️ Unclear/Legacy |
| YAML Version | 1.2 (Strict) | 1.1 & 1.2 (Configurable) | Mixed/Legacy |
| API Style | Functional (Object-based) | Object-Oriented (Tree-based) | Class/Callback |
| Maintenance | ✅ Active | ✅ Active | ❌ Stalled |
For most modern applications, the choice comes down to js-yaml vs yaml.
js-yaml if you treat YAML as a data transport format (like JSON) where you read data, process it, and don't care about the original file's comments or layout. It is fast, strict, and secure.yaml if you are building developer tools, linters, or editors where the YAML file is a source of truth that humans edit. Its ability to preserve comments and formatting makes it indispensable for these use cases.Avoid yamljs entirely. It belongs to an older era of JavaScript development and introduces unnecessary risk and limitation to your stack.
Choose js-yaml if you need a battle-tested, strict parser for configuration files or data exchange where security and spec compliance are critical. It is the ideal choice for CI/CD pipelines, static site generators, or any scenario where you must reject non-standard YAML or prevent prototype pollution attacks via its safe loading modes.
Choose yaml if you are building developer tools, editors, or configuration systems where preserving user comments, whitespace, and file structure is essential. Its tree-based API allows you to modify specific nodes without rewriting the entire file, making it the superior choice for applications that need to read, edit, and write back YAML files without losing original formatting.
Do NOT choose yamljs for any new project. It is effectively deprecated, lacks active maintenance, and does not support modern ES modules or the latest YAML specifications. If you encounter it in a legacy codebase, plan to migrate to js-yaml or yaml immediately to ensure security updates and compatibility with modern build tools.
A fast and complete YAML parser and writer for JavaScript. Supports both the 1.2 and 1.1 specs, and passes the entire YAML Test Suite.
npm install js-yaml
Upgrading from v4? See the v5 migration guide.
Here we cover the most useful methods. If you need advanced details (such as creating your own tags), see the examples for more info.
import { load } from 'js-yaml'
import { readFileSync } from 'node:fs'
// Get document, or throw exception on error
try {
const doc = load(readFileSync('example.yml', 'utf8'))
console.log(doc)
} catch (e) {
console.log(e)
}
Parses string as a single YAML document. Throws YAMLException on error.
This function does not understand multi-document or empty sources; it throws
an exception on those.
[!WARNING] When processing untrusted input, see the security considerations.
options:
filename (default: null) - string to be used as a file path in
error/warning messages.schema (default: CORE_SCHEMA) - specifies a schema to use.
FAILSAFE_SCHEMA - only strings, arrays and plain objects.JSON_SCHEMA - all JSON-supported types.CORE_SCHEMA - a superset of JSON_SCHEMA, accepting more notations for
the same types.YAML11_SCHEMA - adds the legacy YAML 1.1 types (!!binary, !!timestamp,
!!omap, !!pairs, !!set, merge keys <<, and the broader 1.1 scalar
notations).json (default: false) - compatibility with JSON.parse behaviour. If
true, duplicate keys in a mapping override values rather than throwing an
error.maxDepth (default: 100) - limits the nesting depth for collections (does
not take aliases into account).maxTotalMergeKeys (default: 10000) - limits the total number of keys
processed by merge (<<) across one load() / loadAll() call. Set to -1
to disable.maxAliases (default: -1) - limits the number of alias nodes (*ref) per
document. Set to 0 to reject all aliases, or to -1 for no limit.[!NOTE]
The default
CORE_SCHEMAcomes without the!!mergetag. You can easily enable it if needed:import { load, CORE_SCHEMA, mergeTag } from 'js-yaml' load(data, { schema: CORE_SCHEMA.withTags(mergeTag) })
[!WARNING]
The default
mapTagis{}-object based and does not allow complex keys (objects, arrays and so on). That's an intentional choice for convenience. Also, non-string scalar keys, such asnull, numbers or booleans, are converted to strings.In the rare cases where you really need complex keys, use
realMapTagin the schema instead. It stores any key exactly as provided, at the cost of less convenient access.
See examples for advanced customization approaches.
Same as load(), but understands multi-document sources. Returns an array of
documents.
import { loadAll } from 'js-yaml'
console.log(loadAll(data))
Serializes object as a YAML document. By default it can dump every supported
YAML type, so it throws an exception if you try to dump regexps or functions.
However, you can disable exceptions by setting the skipInvalid option to
true.
options:
indent (default: 2) - indentation width to use (in spaces).flowLevel (default: -1) - nesting level at which collections switch from
block to flow style (-1 means never).seqNoIndent (default: false) - when true, does not add an indentation
level to array elements, ␣␣- 1 => - 1.seqInlineFirst (default: true) - when true, allows a nested collection
to start on the same line after -, -\n - 1 => - - 1.skipInvalid (default: false) - do not throw on invalid types (such as a
function in the schema). Invalid mapping pairs and sequence items are skipped;
undefined sequence items are serialized as null.schema (default: a YAML11_SCHEMA-based schema) - specifies a schema to
use.sortKeys (default: false) - if true, sort keys when dumping YAML. If a
function, use the function to sort the keys.lineWidth (default: 80) - sets the max line width. Set -1 for unlimited
width.noRefs (default: false) - if true, don't convert duplicate objects into
references; inline them instead.quoteStyle (single or double, default: single) - quoting style to use
when a string needs quotes.forceQuotes (default: false) - if true, quote all non-key strings,
using quoteStyle.flowBracketPadding (default: false) - add spaces inside flow collection
brackets, {a: 1} => { a: 1 }.flowSkipCommaSpace (default: false) - omit the space after commas in
flow collections, [1, 2] => [1,2].flowSkipColonSpace (default: false) - omit the space after : in flow
mappings, {a: 1} => {a:1}.quoteFlowKeys (default: false) - quote flow mapping keys, {a: 1} =>
{"a": 1}.tagBeforeAnchor (default: false) - print an explicit tag before an
anchor, &ref_0 !!set => !!set &ref_0.transform - a function (documents: Document[]) => void that can mutate the
generated AST before it is rendered.See examples for advanced customization approaches.
The list of standard YAML tags and corresponding JavaScript types. See also YAML tag discussion and YAML types repository.
!!null '' # null
!!bool 'true' # bool
!!int '3...' # number
!!float '3.14...' # number
!!str '...' # string
!!seq [ ... ] # array
!!map { ... } # object (or Map)
The types below are only available in YAML11_SCHEMA (not in the default
CORE_SCHEMA):
!!binary '...base64...' # Uint8Array
!!timestamp 'YYYY-...' # date
!!set { ... } # Set
# Legacy YAML 1.1 compatibility only; these types cannot be dumped.
!!omap [ ... ] # array of key-value pairs
!!pairs [ ... ] # array of array pairs
To preserve complex keys in the first position of a !!pairs item, replace
the default object-based map with realMapTag in the schema.
JavaScript-specific tags
See js-yaml-js-types for extra types.
This can be useful sometimes for a quick check.
npx js-yaml -h
Note: the CLI script comes with minimal options, and there are no big plans to extend it.