rubyguides

YAML

The YAML module provides utilities for parsing and generating YAML (YAML Ain’t Markup Language) data. It’s part of Ruby’s standard library and is the interface to the Psych library, which is the actual YAML engine for Ruby.

That makes the module a practical bridge between human-readable configuration files and Ruby objects. In day-to-day code, you usually use it for settings files, fixtures, or small data exports where readability matters as much as structure.

Overview

This format is a human-friendly data serialization format commonly used for configuration files, data exchange, and object persistence. Ruby’s YAML module converts between Ruby objects and YAML strings:

  • Parse: Convert YAML strings into Ruby objects
  • Generate: Convert Ruby objects into YAML strings

The examples below focus on the parts people reach for most often: loading simple data, dumping Ruby structures back to text, and knowing when to choose the safer APIs. That keeps the page practical without turning it into a full specification reference.

Loading YAML

YAML.load converts a YAML string into Ruby data structures.

require 'yaml'

yaml_string = <<~YAML
  name: Alice
  age: 30
  active: true
  tags:
    - ruby
    - developer
YAML

hash = YAML.load(yaml_string)
hash["name"]    # => "Alice"
hash["age"]     # => 30
hash["tags"]    # => ["ruby", "developer"]

YAML.load Parameters

ParameterTypeDefaultDescription
stringStringRequiredThe YAML string to parse
filenameStringnilFilename for error messages
fallbackObjectfalseValue to return if string is nil/empty

Load Options

# Load multiple documents (returns array)
YAML.load("---\nfoo\n---\nbar\n", permitted_classes: [Date])

# Safe loading (Ruby 3.1+)
YAML.load(yaml_string, permitted_classes: [Date], permitted_symbols: [], aliases: false)

Security Warning: Never use YAML.load with untrusted data. This can execute arbitrary code. Use YAML.safe_load instead for untrusted input.

That warning is worth treating as the default rule, not an edge case. If the input comes from a user, a network request, or an external file you do not fully control, start with the safe loader and only add permissions you actually need.

YAML.safe_load

require 'yaml'

# Safe loading with whitelist
safe_yaml = YAML.safe_load(untrusted_input, 
  permitted_classes: [Date, Symbol],
  permitted_symbols: [],
  aliases: false
)

The safe loader keeps the parsing behavior predictable by requiring you to whitelist what can be materialized. That extra step is usually a good tradeoff when data crosses a trust boundary.

ParameterTypeDefaultDescription
stringStringRequiredThe YAML string to parse
permitted_classesArray[]Whitelist of allowed classes
permitted_symbolsArray[]Whitelist of allowed symbols
aliasesBooleanfalseWhether to allow aliases

Dumping to YAML

YAML.dump converts Ruby objects into YAML strings.

require 'yaml'

data = { 
  name: "Bob", 
  age: 25, 
  skills: ["ruby", "rails"] 
}

yaml_string = YAML.dump(data)
# => "---\n:name: Bob\n:age: 25\n:skills:\n- ruby\n- rails\n"

Dumping is the reverse of loading, but it is still useful to read the output with care. Small formatting choices, such as indentation and key style, can affect how easy the file is for someone else to edit later.

YAML.dump Parameters

ParameterTypeDefaultDescription
objectObjectRequiredRuby object to convert
ioIOnilOutput stream (optional)
optsHash{}Dumping options

Dump Options

# Pretty-printed output
puts YAML.dump({ name: "Carol", items: [1, 2, 3] })
# => ---
# :name: Carol
# :items:
# - 1
# - 2
# - 3

# Indentation
YAML.dump({ a: { b: { c: 1 } } }, indent: 4)

These two examples show that dumping is not just about turning Ruby objects into text. The formatting options also matter when you want the output to stay readable and easy to diff, especially if the file will be checked into version control.

You can also use the alias YAML.generate:

YAML.dump({ key: "value" })
YAML.generate({ key: "value" })  # Same thing

That alias matters mostly for readability. If your code already talks about generating output, generate can read a little more naturally than dump even though the result is the same.

The alias is handy when the surrounding code already uses the word “generate” for other text output. It does the same job, so the choice is mostly about readability in context.

Parsing YAML

YAML.parse parses YAML into a generic AST representation rather than Ruby objects.

require 'yaml'

ast = YAML.parse("{ name: Alice, age: 30 }")
# => #<Psych::Nodes::Mapping ...>

ast.children[0].value  # => "name"
ast.children[1].value  # => "Alice"

This is useful when you need to inspect the YAML structure without converting to Ruby objects.

It can help with validation or debugging, especially when you want to see the shape of the document before deciding whether to load it into application objects.

YAML.parse Parameters

ParameterTypeDefaultDescription
stringStringRequiredThe YAML string to parse
filenameStringnilFilename for error messages

Practical Examples

Configuration Files

require 'yaml'

# Read config
config = YAML.load_file('config.yml')

# Write config
File.write('config.yml', YAML.dump({ database: { host: "localhost", port: 5432 } }))

This is the common read-and-write loop for config files: load the settings, make a small change, and write them back. In real projects, you would usually keep the file contents small and deterministic so diffs stay readable.

When the file format is meant for humans, that predictability pays off quickly. The person reviewing the change can see exactly what moved, which is much easier than deciphering a huge blob of regenerated text.

Object Serialization

require 'yaml'

class User
  attr_accessor :name, :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
end

user = User.new("Dan", "dan@example.com")
yaml_string = YAML.dump(user)
# => !ruby/object:User
# :name: Dan
# :email: dan@example.com

# Load back
loaded = YAML.load(yaml_string)
loaded.name  # => "Dan"

YAML can serialize simple Ruby objects, but that convenience comes with the same trust warning from earlier. Only reload data you control, and keep the object model simple when you need long-term compatibility.

In other words, serialization is convenient, but it is not a free pass to move arbitrary object graphs around. Keep the example small, and prefer plain data when the file needs to outlive the current codebase.

Loading multiple documents

require 'yaml'

multi_doc = <<~YAML
  ---
  name: First
  ---
  name: Second
  ---
  name: Third
YAML

documents = YAML.load_stream(multi_doc)
documents.each { |doc| puts doc["name"] }
# First
# Second
# Third

Multiple-document data is useful when you want one file to hold several related records. It is a good fit for fixtures or batch config, as long as the consuming code expects that structure.

That structure becomes especially handy when each document needs to be processed independently. You can stream the records and decide how to handle each one without flattening the entire file up front.

Ruby version history

  • Ruby 1.8: Introduced YAML module with Syck engine
  • Ruby 2.0: Removed Syck, Psych became the default
  • Ruby 3.1: Added permitted_classes and permitted_symbols parameters to safe_load

See Also