JSON-to-YAML in JavaScript is a one-liner with any reasonable library. The interesting bits are output style (block vs flow), key ordering, and round-trip support. js-yaml covers the basics; the eemeli/yaml package covers everything.

Method 1: js-yaml (the standard option)

js-yaml is the most-downloaded YAML library on npm. Lightweight (~150KB), works in browser and Node, supports YAML 1.1.

npm install js-yaml
import yaml from "js-yaml";
import fs from "node:fs";

function jsonToYaml(inPath, outPath) {
  const data = JSON.parse(fs.readFileSync(inPath, "utf8"));
  const yamlText = yaml.dump(data, {
    sortKeys: false,    // preserve JSON insertion order
    lineWidth: -1,      // don't wrap long lines
    noRefs: true,       // disable anchors/aliases
  });
  fs.writeFileSync(outPath, yamlText);
}

jsonToYaml("config.json", "config.yaml");

For browser use:

import yaml from "js-yaml";

document.querySelector("input[type=file]").addEventListener("change", async (e) => {
  const text = await e.target.files[0].text();
  const yamlText = yaml.dump(JSON.parse(text), { sortKeys: false, lineWidth: -1 });
  const blob = new Blob([yamlText], { type: "text/yaml" });
  const url = URL.createObjectURL(blob);
  const a = Object.assign(document.createElement("a"), { href: url, download: "out.yaml" });
  a.click();
});

js-yaml is YAML 1.1 by default — that means 'yes', 'no', 'on', 'off' get coerced to booleans. To force YAML 1.2 behavior, pass schema: yaml.JSON_SCHEMA.

Method 2: yaml (eemeli, full YAML 1.2 + round-trip)

The yaml package by Eemeli Aro is the most spec-compliant YAML library on npm. Slightly larger than js-yaml (~250KB) but supports YAML 1.2 fully and preserves comments on round-trips.

npm install yaml
import { stringify } from "yaml";
import fs from "node:fs";

function jsonToYaml(inPath, outPath) {
  const data = JSON.parse(fs.readFileSync(inPath, "utf8"));
  const yamlText = stringify(data, {
    indent: 2,
    lineWidth: 0,        // 0 = no wrapping
    minContentWidth: 0,
    blockQuote: "literal",  // multi-line strings use | instead of escape codes
  });
  fs.writeFileSync(outPath, yamlText);
}

jsonToYaml("config.json", "config.yaml");

For round-trip work (parse YAML, modify, write back without losing comments):

import { parseDocument } from "yaml";
import fs from "node:fs";

const doc = parseDocument(fs.readFileSync("original.yaml", "utf8"));
doc.set("version", "1.2.0");
fs.writeFileSync("modified.yaml", String(doc));  // comments preserved

Use this package when YAML output will be edited by humans (k8s configs, Helm charts, GitHub Actions). Use js-yaml when YAML is purely machine-generated.

Method 3: ChangeThisFile API (no library, edge-friendly)

If you're on Cloudflare Workers, Vercel Edge, or want to keep your bundle small, the API does the conversion in one fetch call. Free tier gives 100 conversions/month.

const API_KEY = "ctf_sk_your_key_here";

async function jsonToYaml(jsonText, filename = "input.json") {
  const form = new FormData();
  form.append("file", new Blob([jsonText], { type: "application/json" }), filename);
  form.append("source", "json");
  form.append("target", "yaml");

  const response = await fetch("https://changethisfile.com/v1/convert", {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form,
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  return await response.text();
}

const yaml = await jsonToYaml(JSON.stringify({ name: "app", version: "1.0" }));
console.log(yaml);

The API also accepts JSON5 (with comments) and JSONC inputs — useful for converting tsconfig.json or VS Code config files which neither js-yaml nor yaml-package handle natively.

When to use each

ApproachBest forTradeoff
js-yamlDefault for most JS projects, smallest bundleYAML 1.1 only by default, loses comments on round-trips
yaml (eemeli)YAML 1.2, round-trip work, k8s/Helm configsSlightly larger bundle
ChangeThisFile APIEdge runtimes, JSON5/JSONC inputs, multi-language teamsNetwork call, per-call cost

CLI alternative: yq or json2yaml

For one-off conversions, yq is the fastest CLI option (Go-based, no Node startup cost).

brew install yq
yq -P -oy config.json > config.yaml

# Or via npm:
npm install -g json2yaml
json2yaml config.json > config.yaml

The npm json2yaml package is just a CLI wrapper around js-yaml. yq uses Go's YAML library — slightly different defaults but functionally equivalent output.

Production tips

  • Set lineWidth: -1 (js-yaml) or 0 (yaml package). Both libraries wrap long strings by default, which makes diffs ugly when configs change. Disable wrapping for stable diffs.
  • Preserve key order. sortKeys: false in js-yaml, default in yaml package. Sorted output makes config files harder to scan because related fields scatter alphabetically.
  • Use the JSON schema for round-trip safety. If you do JSON→YAML→JSON cycles, set js-yaml's schema: yaml.JSON_SCHEMA so the dump uses the same type assumptions as JSON.
  • Quote ambiguous strings. '01' as YAML 1.1 becomes int 1. The eemeli yaml package does this correctly by default; js-yaml needs schema: yaml.CORE_SCHEMA.

For most projects, js-yaml is enough. For comment-preserving round-trips, the eemeli yaml package. For edge runtimes or unusual JSON dialects, the API. Free tier is 100 conversions/month, no card.