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.

```ts
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:

```ts
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`

```ts
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:

```ts
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`:

```ts
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

```ts
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

```ts
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

```ts
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:

```ts
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

```ts
// 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

```ts
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:

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

redis:
  host: localhost
  port: 6379

features:
  auth: true
  rateLimit: true
  analytics: false
```

#### Default Import

```ts#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:

```ts
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:

```ts
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:

```js
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.yaml` → `config.yaml.d.ts`):

```yaml#config.yaml
features: "advanced"
server:
  host: localhost
  port: 3000
```

```ts#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:

```ts#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

```yaml#config.yaml
server:
  port: 3000
  host: localhost

features:
  debug: true
  verbose: false
```

```ts#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:

```bash
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:

```yaml#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
```

```ts#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

```yaml#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
```

```ts#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

```yaml#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
```

```ts#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:

```bash
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:

```ts#Load configuration based on environment
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");
  }
}
```
