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
| Parameter | Type | Default | Description |
|---|---|---|---|
string | String | Required | The YAML string to parse |
filename | String | nil | Filename for error messages |
fallback | Object | false | Value 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.
| Parameter | Type | Default | Description |
|---|---|---|---|
string | String | Required | The YAML string to parse |
permitted_classes | Array | [] | Whitelist of allowed classes |
permitted_symbols | Array | [] | Whitelist of allowed symbols |
aliases | Boolean | false | Whether 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
| Parameter | Type | Default | Description |
|---|---|---|---|
object | Object | Required | Ruby object to convert |
io | IO | nil | Output stream (optional) |
opts | Hash | {} | 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
| Parameter | Type | Default | Description |
|---|---|---|---|
string | String | Required | The YAML string to parse |
filename | String | nil | Filename 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_classesandpermitted_symbolsparameters tosafe_load