Ruby YAML
last modified October 18, 2026
YAML (YAML Ain't Markup Language) is a human-friendly data serialization format
widely used for configuration files and data exchange. Ruby ships with the
Psych library, which is accessed through require 'yaml'. This guide
covers loading and dumping YAML, the difference between YAML.load
and YAML.safe_load, file I/O, YAML syntax, multi-line strings, and
error handling. For a companion article on JSON, see the
Ruby JSON tutorial.
Loading YAML with YAML.load
YAML.load parses a YAML string and returns the corresponding Ruby
objects. Mappings become hashes, sequences become arrays, and scalars are
converted to strings, integers, floats, booleans, or nil. The
require 'yaml' line must come first. This example parses a small
document describing a person.
#!/usr/bin/env ruby
require 'yaml'
yaml_text = <<~YAML
name: Alice
age: 30
languages:
- Ruby
- Python
active: true
YAML
data = YAML.load(yaml_text)
puts data.inspect
puts data["name"]
puts data["languages"].first
puts data["age"].class
YAML uses indentation (spaces only, never tabs) for structure. A
key: value line makes a mapping, a dash starts a sequence item, and
nesting is created by indenting under a parent key. Flow style such as
[admin, editor] is also valid. Lists, nested mappings, and
multi-line strings (| keeps line breaks, > folds
them) all map to Ruby arrays, hashes, and strings. Symbol keys are written with
a leading colon (:host: localhost) and need
permitted_classes: [Symbol] under safe_load.
The heredoc holds the YAML text, and YAML.load turns it into a
Ruby hash. Keys are strings by default, the list becomes an array, and
age is parsed as an Integer.
$ ./basic_load.rb
{"name"=>"Alice", "age"=>30, "languages"=>["Ruby", "Python"], "active"=>true}
Alice
Ruby
Integer
Why YAML.safe_load Is Preferred
YAML.load can instantiate arbitrary Ruby objects and symbols from
tags in the input, which makes it unsafe for untrusted data. Ruby 3.0 still
allows it for trusted input, but for anything that comes from users or the
network, YAML.safe_load is the right choice. It only builds basic
types (strings, numbers, booleans, arrays, hashes, and nil) and
raises Psych::DisallowedClass for anything else. This example
shows both behaviors.
#!/usr/bin/env ruby
require 'yaml'
untrusted = "greeting: hello\nnumbers: [1, 2, 3]\n"
puts YAML.safe_load(untrusted).inspect
begin
YAML.safe_load(":mode: fast\n")
rescue Psych::DisallowedClass => e
puts "Rejected: #{e.message}"
end
config = YAML.safe_load(":mode: fast\n", permitted_classes: [Symbol])
puts config.inspect
begin
YAML.safe_load("--- !ruby/object:Object {}\n")
rescue Psych::DisallowedClass => e
puts "Rejected: #{e.message}"
end
Plain data loads without any extra options. The symbol key
:mode is rejected until Symbol is listed in
permitted_classes:, so only permit classes you actually expect.
The !ruby/object tag is refused entirely. On Ruby 3.0.2,
YAML.load accepts both of these inputs, which is why it should not
be used on untrusted data.
$ ./safe_load.rb
{"greeting"=>"hello", "numbers"=>[1, 2, 3]}
Rejected: Tried to load unspecified class: Symbol
{:mode=>"fast"}
Rejected: Tried to load unspecified class: Object
Dumping Ruby Objects to YAML
YAML.dump serializes a Ruby object to a YAML string, and every
object also responds to to_yaml. Hashes become mappings, arrays
become sequences with dashes, and nested structures are indented. The example
dumps a nested hash and an array of mixed types.
#!/usr/bin/env ruby
require 'yaml'
book = {
"title" => "Ruby Basics",
"year" => 2024,
"tags" => ["ruby", "tutorial"],
"author" => { "name" => "Jan", "email" => "jan@example.com" }
}
puts YAML.dump(book)
puts "---"
puts [1, "two", 3.0, nil, true].to_yaml
YAML.dump(book) writes the nested hash with the nested
author mapping indented beneath its key. The array is dumped with
to_yaml, which produces the same format. The nil
value appears as an empty entry after the dash.
$ ./dump_to_yaml.rb --- title: Ruby Basics year: 2024 tags: - ruby - tutorial author: name: Jan email: jan@example.com --- --- - 1 - two - 3.0 - - true
Reading and Writing YAML Files
YAML.load_file reads and parses a file in one step. To write, pass
the result of to_yaml to File.write. Note that
load_file uses the same unrestricted loader as
YAML.load, so for files you do not control, prefer
YAML.safe_load(File.read(path)). The example writes a settings file,
reads it back, and deletes it.
#!/usr/bin/env ruby
require 'yaml'
settings = { "theme" => "dark", "font_size" => 14, "plugins" => ["git", "lint"] }
File.write("settings.yml", settings.to_yaml)
loaded = YAML.load_file("settings.yml")
puts loaded.inspect
puts "Theme: #{loaded['theme']}"
File.delete("settings.yml")
File.write stores the YAML produced by to_yaml.
YAML.load_file reads it back into an equivalent hash. The file is
removed at the end so the example leaves nothing behind.
$ ./file_io.rb
{"theme"=>"dark", "font_size"=>14, "plugins"=>["git", "lint"]}
Theme: dark
Multi-line Strings
YAML has two block scalar styles for multi-line text. The literal style
(|) keeps each line break exactly as written. The folded style
(>) joins lines into a single paragraph with spaces, and a blank
line inside it produces a newline. Both styles end with a single newline by
default. The example loads one of each.
#!/usr/bin/env ruby
require 'yaml'
yaml_text = <<~YAML
literal: |
line one
line two
folded: >
this text is
folded into one line
YAML
data = YAML.safe_load(yaml_text)
p data["literal"]
p data["folded"]
The literal value keeps its two lines separated by \n.
The folded value joins its two source lines into one, with a
space in place of the line break.
$ ./multiline.rb "line one\nline two\n" "this text is folded into one line\n"
Handling Syntax Errors
Malformed YAML raises Psych::SyntaxError. Rescue it when parsing
user-supplied text so the application can report the problem instead of
crashing. The example shows two malformed documents being rescued.
#!/usr/bin/env ruby
require 'yaml'
bad = "name: Alice\n age: 30\n - broken"
begin
YAML.safe_load(bad)
rescue Psych::SyntaxError => e
puts "Syntax error caught"
puts e.class
end
begin
YAML.safe_load("key: [unclosed")
rescue Psych::SyntaxError => e
puts "Still caught: #{e.class}"
end
Both inputs are invalid YAML, and Psych::SyntaxError is raised for
each. Catching it lets the program recover, log the error, or ask for corrected
input.
$ ./bad_input.rb Syntax error caught Psych::SyntaxError Still caught: Psych::SyntaxError
List all Ruby tutorials.
Author
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.