YAML Strings Quoting and When You Really Need Quotes
Plain, single quoted and double quoted YAML strings compared, with the characters that force quotes and a table of values that silently change type.

YAML lets you write most strings with no quotes at all, which is its charm and its main source of surprises. The parser decides what an unquoted word is by looking at it, and "looking at it" includes treating yes as a boolean, 010 as an octal number and 12:30 as a count of seconds. There are three ways to write a string, they differ in what they allow, and a handful of rules tell you when quotes are optional and when they are required. This post gives the rules and the evidence.
Three ways to write a string
Plain (unquoted). name: web server. The string runs to the end of the line, minus a trailing comment, and leading and trailing spaces are dropped. Plain scalars cannot contain : (colon and space) or # (space and hash), and cannot begin with several characters that have meaning in YAML.
Single-quoted. name: 'web server'. Everything inside is literal: no escapes, so a backslash is a backslash. The only special sequence is two single quotes in a row, which stand for one.
Double-quoted. name: "web\tserver". Backslash escapes are processed: \n newline, \t tab, \\ backslash, \" quote, \u00e9 a Unicode character.
Loading a document with all three kinds of trouble:
a: "tab\there\nnewline"
b: 'it''s'
c: "\u00e9"{'a': 'tab\there\nnewline', 'b': "it's", 'c': 'é'}The double-quoted value contains a real tab and a real line break (Python prints them as \t and \n). The single-quoted value contains one apostrophe from the two. The escape \u00e9 became the letter é. Use single quotes when the text has backslashes you want to keep, such as Windows paths or regular expressions ('C:\temp', '\d+'), and double quotes when you need escapes or a control character.
When a plain string is not a string
Before the parser treats a plain scalar as text, it tries to resolve it as a null, boolean, number or timestamp. If it succeeds, the value is not a string any more. A table of what happened to common values, loaded with PyYAML (which follows YAML 1.1) and ruamel.yaml (YAML 1.2):
| Written | PyYAML | ruamel (1.2) |
|---|---|---|
yes | True | 'yes' |
no | False | 'no' |
on, off | True, False | 'on', 'off' |
NO | False | 'NO' |
y, n | 'y', 'n' | 'y', 'n' |
010 | 8 | 10 |
0x1F | 31 | 31 |
1e3 | '1e3' | 1000.0 |
12:30 | 750 | '12:30' |
~, null, Null | None | None |
2026-01-05 | a date | a date |
1.10 | 1.1 | 1.1 |
The two columns differ because the two versions of the language differ (YAML 1.1 versus 1.2). What they share is the lesson: an unquoted value that looks like anything other than text is converted, and the conversion drops information. 1.10 becomes 1.1, and a version string is gone. NO becomes False, which is why the country code for Norway is a famous victim (the Norway problem and YAML boolean surprises). 12:30 becomes 750 in YAML 1.1, because a colon between digits is read as base 60 (12 times 60 plus 30).
The rule that follows: if a value must be a string and could be read as another type, quote it. That covers codes, identifiers, version numbers, times, phone numbers with leading zeros and anything from a list of country or language codes.
Characters that force quotes
Even when a value is clearly text, some characters have syntax meaning in a plain scalar:
A colon followed by a space (
:) starts a mapping value.msg: Error: disk fullfails with "mapping values are not allowed here".A space followed by a hash (
#) starts a comment.color: red #fffloads asred.A leading
*or&starts an alias or an anchor.pattern: *.logfails while scanning an alias.A leading
!starts a tag, and%a directive.bang: !importantfails with "could not determine a constructor for the tag".A leading
@or a backtick is reserved, andtoken: @abc123fails with "found character '@' that cannot start any token".A leading
-,?or:followed by a space starts a list item, a complex key or a mapping.A leading
{,[,|,>or a quote character starts flow style, a block scalar or a quoted string.A trailing colon, or a value that is only a dash or a question mark, are also structure.
The fix is the same each time: wrap the value in quotes. Double quotes if it contains a single quote or needs an escape; single quotes otherwise. msg: "Error: disk full" and pattern: '*.log' both load as text (fixing special characters that break YAML values shows the failures and their messages).
A policy
You can quote everything, and some teams do, because it removes the whole class of surprises. The cost is noise and a loss of the visual simplicity that makes YAML pleasant. A middle path that works:
Leave ordinary words, paths and URLs unquoted when they contain none of the characters above.
Quote anything that is a code, identifier or version, however safe it looks today.
"3.10","NO","00123".Quote anything with
:or#or a leading special character.Lint it:
yamllinthas aquoted-stringsrule that can enforce a style, and atruthyrule that warns onyesandon.Validate types with a schema, so that a number where a string belongs is an error, not a surprise (validating YAML configuration with pydantic).
When a program writes YAML, let the library choose quotes. It knows which strings would be misread and quotes exactly those.
Related reading
The cluster hub is YAML syntax you need to stop guessing at. For numbers, dates and the rest of the surprising conversions, see why a number turns into something else in YAML. Block strings for long text are in YAML multiline strings with pipe and greater than. The scalar styles are defined in section 7 of the YAML 1.2.2 specification, and the quoted-strings lint in the yamllint rule list. Try a value in the YAML to JSON tool to see its type.
When quoting does not protect a value
The consumer re-parses the string. A string that is later put into a shell command, a SQL statement or another YAML document needs the quoting rules of that language too. YAML quotes protect the YAML layer only.
A template engine runs first. In tools that render templates before parsing, the template can inject characters that break the quoting. Parse first, substitute after (environment variable substitution in YAML config).
The type is decided later. A quoted "5" is a string, and a tool that converts strings to numbers by itself will convert it anyway. Test the tool, not only the YAML.
Round trips lose the style. Some libraries rewrite quotes when they save a file. If the quotes matter to the human reader, use a round-trip library (preserving comments and order with ruamel yaml).


