YAML Null Values and the Empty String Question Answered
Six ways to write null in YAML, how they differ from the empty string, and what a key with no value, an empty list and an absent key mean to your code.

Every configuration format has to say "nothing here", and YAML says it in several ways. The ways overlap with the empty string, the empty list and the absent key, and applications treat these differently. A setting that is null can mean "use the default", while the same setting as "" can mean "disable". This post lists the spellings, shows what they load as and gives a convention that keeps the meaning intact.
The spellings of null
YAML's null type has a set of accepted spellings. The core ones: an empty value, a tilde ~, and the words null, Null and NULL. Loaded together:
a:
b: ~
c: null
d: ''
e: ""
f: Null{'a': None, 'b': None, 'c': None, 'd': '', 'e': '', 'f': None}Keys a, b, c and f are null. Keys d and e, the two quoted forms with nothing between the quotes, are empty strings. That is the first and most important distinction: quotes make it a string. Without quotes, nothing means null.
The empty-value form is the one that trips people. A key with a colon and nothing after it, a:, is null. People write it expecting an empty mapping (because more keys will be added), an empty list or just a placeholder, and get null. If the next lines are indented more, they are the value; if not, the value is null.
Empty collections are not null
An empty list or mapping has its own spelling:
a: []
b: {}
c:{'a': [], 'b': {}, 'c': None}[] is a list with no items, {} a mapping with no entries, and c: is null. In code, for x in cfg["c"] raises TypeError: 'NoneType' object is not iterable, while the same loop over a runs zero times. A frequent cause is a list whose entries were all commented out for a test:
features:
# - search
# - exportThere are no items, so features is null, not []. The fix in the file is to write features: []. The fix in the code is to treat null as empty where that is what you mean: for x in cfg.get("features") or [].
Three states your code sees
A setting has three possible states, and the loader exposes them differently:
| In the file | Python result | Typical meaning |
|---|---|---|
| Key absent | KeyError / .get() returns the default | Not configured, use the default |
key: or key: null | None | Explicitly unset |
key: "" | '' | Explicitly empty |
The three can mean three things. In a database settings file, an absent password may mean "use the environment", password: null "no password, connect without one", and password: "" a configured empty password that the server may reject. Teams run into this when a setting is overridden by another file: an override that says timeout: null can mean "remove the timeout" or can mean nothing, depending on how the merge treats nulls. In the deep-merge function used elsewhere in this cluster, a null in the override replaces the base value with null (debug: ~ in the second file turned debug: false into None), which is the right behavior for "explicitly unset" and a surprise if you wanted "no change" (merging two YAML files in Python with a deep merge shows it).
A convention that holds up
Absent means use the default. The file is silent, the program decides.
Null means explicitly none. Write
~ornullso a reader sees it was intended, and not a barekey:which looks like a forgotten value.Empty string means present but empty. Use
""with quotes, and expect that some consumers will treat it as an error.Empty list means none of these. Write
[].Document it in the schema. Which keys are nullable, and what null means for each.
A schema makes it enforceable. In JSON Schema, a nullable string is {"type": ["string", "null"]}, and a field that is not nullable rejects null. In pydantic, str | None is nullable and str is not (validating YAML configuration with pydantic). The loader then fails with the key name when someone writes host: and forgets the value, instead of passing None into a connection function and failing in a stack trace three layers down.
Null in lists and in keys
A list can contain nulls: [a, ~, b] loads as ['a', None, 'b']. A dash with nothing after it, - , is a null item. That is usually a mistake from a deleted line. Keys can be null as well, since a key is any scalar: a line : value or ~: value makes a None key. Those exist in the specification and almost never mean what the author wanted; a linter or schema should reject them.
Remember that other languages differ in what they do with null: Python's None, JavaScript's null, Go's zero value for a typed field, which makes absent, null and empty the same thing in a struct. If a YAML file is read by a Go program with struct tags, password: null and password: "" and an absent key may all produce "", and the three states cannot be told apart without pointer fields. Know the target language.
Round trips and conversions
When you convert YAML to JSON, null becomes null, empty string "", and the three states survive. When you convert to CSV, they merge into an empty cell, and the distinction is lost (converting YAML to JSON with yq covers the JSON direction). When a library dumps YAML, it writes null for None (PyYAML) or an empty value (some tools), so a round trip through a program can change ~ into null. That is harmless, and it shows that the spelling carries no meaning. Use quotes when you mean a string, and use any of the null spellings when you mean null.
Related reading
The cluster hub is YAML syntax you need to stop guessing at. For the list and mapping forms around null, see YAML lists and mappings without confusion, and for how a value changes type, why a number turns into something else in YAML. The null type is defined in the YAML null type page and section 10.3.2 of the YAML 1.2.2 specification. Inspect how a value loads in the YAML to JSON tool.
When null is the wrong way to say nothing
The consumer cannot represent it. Some typed languages and environment-variable based tools turn null into the string "null" or an empty string. Test the real consumer.
You need a default but not an override. If an override file must mean "no change" for a key, omit it. Null means change it to nothing.
The value is required. A required setting that is null should fail validation immediately, not be passed along.
The data is tabular. In CSV and spreadsheets the empty cell stands for several things at once, and the distinction cannot be saved. Choose a format that can.


