Bun

YAML

In Bun, YAML is a first-class citizen alongside JSON and TOML.

Bun provides built-in support for YAML files through both runtime APIs and bundler integration. You can

  • Parse YAML strings with Bun.YAML.parse
  • Stringify JavaScript objects to YAML with Bun.YAML.stringify
  • import & require YAML files as modules at runtime (including hot reloading & watch mode support)
  • import & require YAML files in frontend apps via bun's bundler

Conformance

Bun's YAML parser currently passes over 90% of the official YAML test suite. While we're actively working on reaching 100% conformance, the current implementation covers the vast majority of real-world use cases. The parser is written in Zig for optimal performance and is continuously being improved.

Runtime API

Bun.YAML.parse()

Parse a YAML string into a JavaScript object.

import { YAML } from "bun";
const text = `
name: John Doe
age: 30
email: john@example.com
hobbies:
  - reading
  - coding
  - hiking
`;

const data = YAML.parse(text);
console.log(data);
// {
//   name: "John Doe",
//   age: 30,
//   email: "john@example.com",
//   hobbies: ["reading", "coding", "hiking"]
// }

Multi-document YAML

When parsing YAML with multiple documents (separated by ---), Bun.YAML.parse() returns an array:

const multiDoc = `
---
name: Document 1
---
name: Document 2
---
name: Document 3
`;

const docs = Bun.YAML.parse(multiDoc);
console.log(docs);
// [
//   { name: "Document 1" },
//   { name: "Document 2" },
//   { name: "Document 3" }
// ]

Supported YAML Features

Bun's YAML parser supports the full YAML 1.2 specification, including:

  • Scalars: strings, numbers, booleans, null values
  • Collections: sequences (arrays) and mappings (objects)
  • Anchors and Aliases: reusable nodes with & and *
  • Tags: type hints like !!str, !!int, !!float, !!bool, !!null
  • Multi-line strings: literal (|) and folded (>) scalars
  • Comments: using #
  • Directives: %YAML and %TAG
const yaml = `
# Employee record
employee: &emp
  name: Jane Smith
  department: Engineering
  skills:
    - JavaScript
    - TypeScript
    - React

manager: *emp  # Reference to employee

config: !!str 123  # Explicit string type

description: |
  This is a multi-line
  literal string that preserves
  line breaks and spacing.

summary: >
  This is a folded string
  that joins lines with spaces
  unless there are blank lines.
`;

const data = Bun.YAML.parse(yaml);

Error Handling

Bun.YAML.parse() throws an error if the YAML is invalid:

try {
  Bun.YAML.parse("invalid: yaml: content:");
} catch (error) {
  console.error("Failed to parse YAML:", error.message);
}

Bun.YAML.stringify()

Convert a JavaScript value into a YAML string. The API signature matches JSON.stringify:

YAML.stringify(value, replacer?, space?)
  • value: The value to convert to YAML
  • replacer: Currently only null or undefined (function replacers not yet supported)
  • space: Number of spaces for indentation (e.g., 2) or a string to use for indentation. Without this parameter, outputs flow-style (single-line) YAML

Basic Usage

import { YAML } from "bun";

const data = {
  name: "John Doe",
  age: 30,
  hobbies: ["reading", "coding"],
};

// Without space - outputs flow-style (single-line) YAML
console.log(YAML.stringify(data));
// {name: John Doe,age: 30,hobbies: [reading,coding]}

// With space=2 - outputs block-style (multi-line) YAML
console.log(YAML.stringify(data, null, 2));
// name: John Doe
// age: 30
// hobbies:
//   - reading
//   - coding

Output Styles

const arr = [1, 2, 3];

// Flow style (single-line) - default
console.log(YAML.stringify(arr));
// [1,2,3]

// Block style (multi-line) - with indentation
console.log(YAML.stringify(arr, null, 2));
// - 1
// - 2
// - 3

String Quoting

YAML.stringify() automatically quotes strings when necessary:

  • Strings that would be parsed as YAML keywords (true, false, null, yes, no, etc.)
  • Strings that would be parsed as numbers
  • Strings containing special characters or escape sequences
const examples = {
  keyword: "true", // Will be quoted: "true"
  number: "123", // Will be quoted: "123"
  text: "hello world", // Won't be quoted: hello world
  empty: "", // Will be quoted: ""
};

console.log(YAML.stringify(examples, null, 2));
// keyword: "true"
// number: "123"
// text: hello world
// empty: ""

Cycles and References

YAML.stringify() automatically detects and handles circular references using YAML anchors and aliases:

const obj = { name: "root" };
obj.self = obj; // Circular reference

const yamlString = YAML.stringify(obj, null, 2);
console.log(yamlString);
// &root
// name: root
// self:
//   *root

// Objects with shared references
const shared = { id: 1 };
const data = {
  first: shared,
  second: shared,
};

console.log(YAML.stringify(data, null, 2));
// first:
//   &first
//   id: 1
// second:
//   *first

Special Values

// Special numeric values
console.log(YAML.stringify(Infinity)); // .inf
console.log(YAML.stringify(-Infinity)); // -.inf
console.log(YAML.stringify(NaN)); // .nan
console.log(YAML.stringify(0)); // 0
console.log(YAML.stringify(-0)); // -0

// null and undefined
console.log(YAML.stringify(null)); // null
console.log(YAML.stringify(undefined)); // undefined (returns undefined, not a string)

// Booleans
console.log(YAML.stringify(true)); // true
console.log(YAML.stringify(false)); // false

Complex Objects

const config = {
  server: {
    port: 3000,
    host: "localhost",
    ssl: {
      enabled: true,
      cert: "/path/to/cert.pem",
      key: "/path/to/key.pem",
    },
  },
  database: {
    connections: [
      { name: "primary", host: "db1.example.com" },
      { name: "replica", host: "db2.example.com" },
    ],
  },
  features: {
    auth: true,
    "rate-limit": 100, // Keys with special characters are preserved
  },
};

const yamlString = YAML.stringify(config, null, 2);
console.log(yamlString);
// server:
//   port: 3000
//   host: localhost
//   ssl:
//     enabled: true
//     cert: /path/to/cert.pem
//     key: /path/to/key.pem
// database:
//   connections:
//     - name: primary
//       host: db1.example.com
//     - name: replica
//       host: db2.example.com
// features:
//   auth: true
//   rate-limit: 100

Module Import

ES Modules

You can import YAML files directly as ES modules. The YAML content is parsed and made available as both default and named exports:

config.yaml
database:
  host: localhost
  port: 5432
  name: myapp

redis:
  host: localhost
  port: 6379

features:
  auth: true
  rateLimit: true
  analytics: false

Default Import

app.ts
import config from "./config.yaml";

console.log(config.database.host); // "localhost"
console.log(config.redis.port); // 6379

Named Imports

You can destructure top-level YAML properties as named imports:

import { database, redis, features } from "./config.yaml";

console.log(database.host); // "localhost"
console.log(redis.port); // 6379
console.log(features.auth); // true

Or combine both:

import config, { database, features } from "./config.yaml";

// Use the full config object
console.log(config);

// Or use specific parts
if (features.rateLimit) {
  setupRateLimiting(database);
}

CommonJS

YAML files can also be required in CommonJS:

const config = require("./config.yaml");
console.log(config.database.name); // "myapp"

// Destructuring also works
const { database, redis } = require("./config.yaml");
console.log(database.port); // 5432

TypeScript Support

While Bun can import YAML files directly, TypeScript doesn't know the types of your YAML files by default. To add TypeScript support for your YAML imports, create a declaration file with .d.ts appended to the YAML filename (e.g., config.yamlconfig.yaml.d.ts):

config.yaml
features: "advanced"
server:
  host: localhost
  port: 3000
config.yaml.d.ts
const contents: {
  features: string;
  server: {
    host: string;
    port: number;
  };
};

export = contents;

Now TypeScript will provide proper type checking and auto-completion:

app.ts
import config from "./config.yaml";

// TypeScript knows the types!
config.server.port; // number
config.server.host; // string
config.features; // string

// TypeScript will catch errors
config.server.unknown; // Error: Property 'unknown' does not exist

This approach works for both ES modules and CommonJS, giving you full type safety while Bun continues to handle the actual YAML parsing at runtime.

Hot Reloading with YAML

One of the most powerful features of Bun's YAML support is hot reloading. When you run your application with bun --hot, changes to YAML files are automatically detected and reloaded without closing connections

Configuration Hot Reloading

config.yaml
server:
  port: 3000
  host: localhost

features:
  debug: true
  verbose: false
server.ts
import { server, features } from "./config.yaml";

console.log(`Starting server on ${server.host}:${server.port}`);

if (features.debug) {
  console.log("Debug mode enabled");
}

// Your server code here
Bun.serve({
  port: server.port,
  hostname: server.host,
  fetch(req) {
    if (features.verbose) {
      console.log(`${req.method} ${req.url}`);
    }
    return new Response("Hello World");
  },
});

Run with hot reloading:

bun --hot server.ts

Now when you modify config.yaml, the changes are immediately reflected in your running application. This is perfect for:

  • Adjusting configuration during development
  • Testing different settings without restarts
  • Live debugging with configuration changes
  • Feature flag toggling

Configuration Management

Environment-Based Configuration

YAML excels at managing configuration across different environments:

config.yaml
defaults: &defaults
  timeout: 5000
  retries: 3
  cache:
    enabled: true
    ttl: 3600

development:
  <<: *defaults
  api:
    url: http://localhost:4000
    key: dev_key_12345
  logging:
    level: debug
    pretty: true

staging:
  <<: *defaults
  api:
    url: https://staging-api.example.com
    key: ${STAGING_API_KEY}
  logging:
    level: info
    pretty: false

production:
  <<: *defaults
  api:
    url: https://api.example.com
    key: ${PROD_API_KEY}
  cache:
    enabled: true
    ttl: 86400
  logging:
    level: error
    pretty: false
app.ts
import configs from "./config.yaml";

const env = process.env.NODE_ENV || "development";
const config = configs[env];

// Environment variables in YAML values can be interpolated
function interpolateEnvVars(obj: any): any {
  if (typeof obj === "string") {
    return obj.replace(/\${(\w+)}/g, (_, key) => process.env[key] || "");
  }
  if (typeof obj === "object") {
    for (const key in obj) {
      obj[key] = interpolateEnvVars(obj[key]);
    }
  }
  return obj;
}

export default interpolateEnvVars(config);

Feature Flags Configuration

features.yaml
features:
  newDashboard:
    enabled: true
    rolloutPercentage: 50
    allowedUsers:
      - admin@example.com
      - beta@example.com

  experimentalAPI:
    enabled: false
    endpoints:
      - /api/v2/experimental
      - /api/v2/beta

  darkMode:
    enabled: true
    default: auto # auto, light, dark
feature-flags.ts
import { features } from "./features.yaml";

export function isFeatureEnabled(
  featureName: string,
  userEmail?: string,
): boolean {
  const feature = features[featureName];

  if (!feature?.enabled) {
    return false;
  }

  // Check rollout percentage
  if (feature.rolloutPercentage < 100) {
    const hash = hashCode(userEmail || "anonymous");
    if (hash % 100 >= feature.rolloutPercentage) {
      return false;
    }
  }

  // Check allowed users
  if (feature.allowedUsers && userEmail) {
    return feature.allowedUsers.includes(userEmail);
  }

  return true;
}

// Use with hot reloading to toggle features in real-time
if (isFeatureEnabled("newDashboard", user.email)) {
  renderNewDashboard();
} else {
  renderLegacyDashboard();
}

Database Configuration

database.yaml
connections:
  primary:
    type: postgres
    host: ${DB_HOST:-localhost}
    port: ${DB_PORT:-5432}
    database: ${DB_NAME:-myapp}
    username: ${DB_USER:-postgres}
    password: ${DB_PASS}
    pool:
      min: 2
      max: 10
      idleTimeout: 30000

  cache:
    type: redis
    host: ${REDIS_HOST:-localhost}
    port: ${REDIS_PORT:-6379}
    password: ${REDIS_PASS}
    db: 0

  analytics:
    type: clickhouse
    host: ${ANALYTICS_HOST:-localhost}
    port: 8123
    database: analytics

migrations:
  autoRun: ${AUTO_MIGRATE:-false}
  directory: ./migrations

seeds:
  enabled: ${SEED_DB:-false}
  directory: ./seeds
db.ts
import { connections, migrations } from "./database.yaml";
import { createConnection } from "./database-driver";

// Parse environment variables with defaults
function parseConfig(config: any) {
  return JSON.parse(
    JSON.stringify(config).replace(
      /\${([^:-]+)(?::([^}]+))?}/g,
      (_, key, defaultValue) => process.env[key] || defaultValue || "",
    ),
  );
}

const dbConfig = parseConfig(connections);

export const db = await createConnection(dbConfig.primary);
export const cache = await createConnection(dbConfig.cache);
export const analytics = await createConnection(dbConfig.analytics);

// Auto-run migrations if configured
if (parseConfig(migrations).autoRun === "true") {
  await runMigrations(db, migrations.directory);
}

Bundler Integration

When you import YAML files in your application and bundle it with Bun, the YAML is parsed at build time and included as a JavaScript module:

bun build app.ts --outdir=dist

This means:

  • Zero runtime YAML parsing overhead in production
  • Smaller bundle sizes
  • Tree-shaking support for unused configuration (named imports)

Dynamic Imports

YAML files can be dynamically imported, useful for loading configuration on demand:

Load
const env = process.env.NODE_ENV || "development";
const config = await import(`./configs/${env}.yaml`);

// Load user-specific settings
async function loadUserSettings(userId: string) {
  try {
    const settings = await import(`./users/${userId}/settings.yaml`);
    return settings.default;
  } catch {
    return await import("./users/default-settings.yaml");
  }
}