STXT vs YAML
YAML and STXT use indentation to define the hierarchy. The mental model is different: YAML has maps and lists, like JSON, and STXT has trees of text nodes, like XML.
The same content, twice
A service descriptor in YAML:
service: billing
replicas: 2
ports:
- 8080
- 8443
maintainer: ana@example.com
notes: |
Restarted after the 2026-08-31 incident.
The TLS certificate expires on 2027-03-01.
And in STXT:
Service: billing
Replicas: 2
Ports:
Port: 8080
Port: 8443
Maintainer: ana@example.com
Notes >>
Restarted after the 2026-08-31 incident.
The TLS certificate expires on 2027-03-01.A simple document looks alike in the two. The differences are these:
| YAML | STXT | |
|---|---|---|
| Values | The spelling decides the type: 2 is an integer and "2" a string |
Every value is text. The type, when needed, is declared by the template |
| Lists | Two syntaxes: block (- 8080) and flow ([8080, 8443]) |
There is no list syntax: a repeated child is the list (FAQ) |
| Indentation | Any number of spaces per level, and no tabs | One tab or four spaces per level |
| Multi-line text | Block scalars, with several indicators | A block node >> |
| Namespaces | None | Part of the language (STXT-SPEC §7) |
| Schemas | External tools | Part of the language, and optional |
When the spelling changes the meaning
In YAML, an unquoted value is converted to a type according to its form. The rules changed between versions 1.1 and 1.2 of the language:
country: NO # false in YAML 1.1; the string "NO" in 1.2
version: 3.10 # the number 3.1 in both
window: 12:30 # the integer 750 in YAML 1.1 (sexagesimal); a string in 1.2
enabled: yes # true in 1.1; the string "yes" in 1.2
Keeping NO, 3.10 or 12:30 as text requires quoting them.
In STXT a value is always the characters written:
| Node name | Node value |
|---|---|
| Country | NO |
| Version | 3.10 |
| Window | 12:30 |
| Enabled | yes |
If Enabled must be a boolean, the template says so (Enabled: (1) BOOLEAN).
In that case the validator rejects yes, and does not convert it.
Multi-line text
A YAML block scalar combines several indicators: the style (| literal or > folded),
the final line break (-, + or none) and, when needed, the indentation:
literal: |
first line
second line
folded: >
first line
second line
kept: |+
first line
next: value
Each combination produces a different string:
| Key | Value |
|---|---|
literal |
"first line\nsecond line\n" |
folded |
"first line second line\n" |
kept |
"first line\n\n" (it keeps the empty line that follows) |
STXT has a single form, the block node. The text is literal, with no variants and no escape characters:
Additional indentation and intermediate empty lines are kept. Trailing whitespace on each line and trailing empty lines are discarded (STXT-SPEC §10).
Security
YAML includes two features that STXT does not have:
- Anchors and aliases (
&a,*a): a small document can expand into a huge structure in memory. - Type tags (
!!python/object,!ruby/object): implementation-specific tags have allowed arbitrary objects to be instantiated while loading a document. Hence thesafe_loadfunctions of the libraries.
STXT has no references, anchors, tags, inclusion or entities. Parsing is linear, line by line and in a single pass (STXT-SPEC §15).
Validation
YAML is validated with external tools, usually JSON Schema over the loaded structure. In STXT schemas are part of the language. The descriptor from the beginning, with a namespace:
Service (com.example.deploy): billing
Replicas: 2
Port: 8080
Port: 8443
Maintainer: ana@example.com
Notes >>
The TLS certificate expires on 2027-03-01.And the template for its namespace:
Template (@stxt.template): com.example.deploy
Structure >>
Service (com.example.deploy):
Replicas: (1) NATURAL
Port: (+) NATURAL
Maintainer: (1) EMAIL
Notes: (?) TEXTThe Ports container is not needed here: the (+) cardinality already says that Port repeats.
A document with errors does not validate:
# ERROR: this document does not validate
Service (com.example.deploy): billing
# `two` is not a NATURAL
Replicas: two
# At least one `Port` is missing
# `ana` is not an EMAIL
Maintainer: ana
# `Region` is not in the template
Region: eu-west-1What about TOML?
TOML shares its goal with STXT: files that a person writes and reads. The hierarchy is not in the indentation but in headers that repeat the full path of their table:
[[job]]
name = "database"
[[job.step]]
run = "pg_dump acme > acme.sql"
[[job.step]]
run = "gzip acme.sql"
[[job]]
name = "documents"
[[job.step]]
run = "tar cf documents.tar /srv/docs"
Each [[job.step]] belongs to the last [[job]] opened. In STXT each level is written once,
and a child belongs to the node it is indented under:
Backup:
Job: database
Step: pg_dump acme > acme.sql
Step: gzip acme.sql
Job: documents
Step: tar cf documents.tar /srv/docsAs in YAML, the type of a value is decided by its spelling: 14 is an integer and "14" a string.
Strings always go between quotes, and multi-line ones cannot be indented with their table.
It has no namespaces or schemas.
What about NestedText?
NestedText makes several of the decisions of STXT: every value is text, with no implicit types or escape characters, and the structure is in the indentation. The descriptor from the beginning:
service: billing
replicas: 2
ports:
- 8080
- 8443
maintainer: ana@example.com
notes:
> Restarted after the 2026-08-31 incident.
> The TLS certificate expires on 2027-03-01.
And in STXT:
Service: billing
Replicas: 2
Ports:
Port: 8080
Port: 8443
Maintainer: ana@example.com
Notes >>
Restarted after the 2026-08-31 incident.
The TLS certificate expires on 2027-03-01.2 and 8080 are strings in both, and multi-line text is literal: in NestedText
each line starts with >, and in STXT it goes under a block node.
NestedText keeps the YAML data model (dictionaries, lists and strings), and leaves types and validation to the application. It has no namespaces or schemas.