···1818}
1919```
20202121+## Nullable Fields
2222+2323+ATProto distinguishes three states for a field:
2424+2525+- **Absent** — the field is omitted from the JSON object.
2626+- **Present with a value** — the field is set.
2727+- **Present but null** — the field is explicitly `null`, which is a distinct signal from absent.
2828+2929+Use `T | null` to declare that a field may hold an explicit `null`:
3030+3131+```mlf
3232+record user {
3333+ handle!: string,
3434+ displayName: string | null, // optional; if present, may be null
3535+ bio: string | null,
3636+}
3737+```
3838+3939+When combined with constraints, the `| null` appears after the constraint block:
4040+4141+```mlf
4242+record profile {
4343+ displayName: string constrained {
4444+ maxLength: 64,
4545+ } | null,
4646+}
4747+```
4848+4949+Semantically:
5050+- `handle!: string` — required, must be a non-null string.
5151+- `displayName: string` — optional; if present, must be a non-null string.
5252+- `displayName: string | null` — optional; if present, may be a string or `null`.
5353+- `displayName!: string | null` — required to be present; value may be a string or `null`.
5454+5555+In the generated ATProto Lexicon, nullable fields appear in a top-level `nullable` array alongside the object's `required` array. MLF's `T | null` syntax folds that sibling array into the field's type position for readability.
5656+2157## Primitive Types
22582359MLF supports several primitive types:
···65101**Null:**
66102```mlf
67103record example {
6868- nothing: null, // Always null (rarely used)
104104+ nothing: null, // Always null — rare as a standalone type; see
105105+ // "Nullable Fields" above for the common `T | null` form
69106}
70107```
71108
+14
website/content/docs/language-guide/05-unions.md
···85858686Though defining types separately is often cleaner.
87878888+## Special Case: `T | null`
8989+9090+`null` as a union member has dedicated semantics — it marks the field as
9191+nullable, not as a discriminated union of `T` or the `null` primitive.
9292+See [Nullable Fields](/docs/language-guide/fields/#nullable-fields) for
9393+details. The `| null` clause is structural, not a regular ref member, and
9494+is always lowered to the ATProto spec's top-level `nullable` array.
9595+9696+```mlf
9797+record profile {
9898+ displayName: string | null, // nullable field, not an open union
9999+}
100100+```
101101+88102## Unions in Arrays
8910390104Unions work in arrays: