STXT Core
1. Introduction
This document is STXT-SPEC, the base specification of the language; the other specifications cite it by that name.
STXT is a Human-First language, designed so that its natural form is readable, clear, and comfortable for people, while at the same time maintaining a precise and easily machine-processable structure.
STXT is a structured, indentation-based text language oriented to:
- Representing documents and data clearly.
- Being simple to read and write.
- Being trivial to parse in any language.
- Allowing both structured content and free text.
- Extending its semantics through
@stxt.schemaor@stxt.template. - Facilitating the creation of parsers while trying to minimize security errors.
1.1 Date and status of this specification
This specification carries no version number. It carries, in its Metadata, a date and a
status:
Last modifis the date of the last modification of the text, whatever its scope: a typo or a change of rule. The document is what it is on that date, and there is no version other than the current one. A date identifies a text: "STXT-SPEC 2026-09-07" designates this specification as it stood on that day.Statussays how much stability is promised. It has four possible values, in this order:
| Status | Promise |
|---|---|
Genesis |
Under construction. Anything MAY change without notice. |
Aurora |
Usable. An incompatible change is possible, expected to be rare, and MUST be announced. |
Zenith |
Stable. A valid document stays valid forever and keeps its meaning: the specification MAY only add. An incompatible change MUST NOT be made; if one were ever needed, it would take the form of a new specification, with its own name, living alongside this one. |
Twilight |
Closed. It changes no more, except for errata. It signals that a successor exists or that the specification is being retired. |
An incompatible change is one by which a document that was valid stops being valid or changes meaning: a deliberate change of the specification's intent. A clarification that sharpens the text without changing that intent is not one.
The status only moves forward, and in that order: a specification MUST NOT return to
an earlier status, and Twilight is terminal. Each step is announced with the date on which
it is taken.
Each STXT specification carries its own date and its own status, independent of the others'.
This specification is in Zenith: the base syntax is what defines what an STXT document is,
and what is valid today will stay valid. "STXT", without further qualification, designates
this specification.
A conforming implementation SHOULD expose the date of the specification it implements
(for instance, as a SPEC_VERSION constant with a YYYY-MM-DD value), distinct from its
package version: two implementations with different packages read the same STXT.
Conformance is declared against the specification and demonstrated with its conformance kit,
which carries its own date and pins the date of every specification it certifies; never
against the version of a package.
2. Terminology
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" must be interpreted according to RFC 2119 and RFC 8174: they carry that meaning only when they appear in uppercase, as here.
3. Document Encoding
An STXT document SHOULD be encoded in UTF-8 without BOM.
A parser:
- SHOULD accept documents that begin with a BOM and, if it accepts them, MUST
discard the leading
U+FEFFbefore processing the first line: the BOM is not part of the content. (If it were not discarded it would be content — it is not a blank, section 4 — and would make the name of the first node invalid.) - MAY emit a warning for documents that begin with a BOM.
Byte input. This specification defines the document over characters; decoding belongs
to the tool that reads the bytes. A tool that reads the input as bytes MUST reject it
if it is not valid UTF-8, instead of substituting the invalid sequences (for example with
U+FFFD): silent substitution makes two tools see different documents from the same
bytes. It is a read error, prior to parsing — like a missing file — and carries no syntax
error code.
Line endings: a parser MUST accept the LF (\n) and CRLF (\r\n) terminators.
If the line ends in CRLF, the trailing \r MUST be discarded before processing the line:
it never forms part of an inline value or of the text of a block. Other terminators
(e.g. a lone CR) are not recognized as line endings. As a matter of style, LF SHOULD be used.
4. Syntactic Unit: Node
Each non-empty line in the document that is not a comment nor part of a >> block defines a node.
Blank. Throughout this specification, blank (or "spaces and tabs") designates
exactly two characters: the space U+0020 and the tab U+0009. They are the only ones
that count as indentation (section 8), the only ones trimmed when normalizing names
and values (section 10) and the only ones that make a line empty. Any other
Unicode space character — the no-break space U+00A0, the ideographic space U+3000,
the line separator U+2028, etc. — and any control character is content: it is part
of the name or value, it is not trimmed and it does not make a line empty. A parser
MUST NOT rely on its platform's notion of "whitespace" for this when that notion is
broader. The \r of a CRLF ending is discarded beforehand, per section 3, and
plays no role here.
An empty line is a line with no characters or composed only of blanks. Outside a
>> block, empty lines are ignored and their indentation is not validated; inside a
block, those that precede more text are preserved, and the final ones are discarded
when the block closes (see section 10.3).
There are two forms of node:
- Inline container node (INLINE node):
Node name: Inline value - Text block node (BLOCK node):
Node name >>
The node name cannot be empty. A line with only : or >> is not valid.
Example with INLINE nodes:
Example with a BLOCK node:
Block node >>
This is the content
of the text block:
- Leading spaces and line breaks are preserved
- Right trim is applied
- Left trim is NOT appliedA node may optionally include a namespace:
4.1 Normalization of the node name
The node name is taken from the text between:
- The first character not belonging to the indentation, and
- The first character belonging to any of:
- The start of a namespace
(, - The character
:, - The operator
>>,
- The start of a namespace
On that fragment, the following is applied:
- Removal of trailing spaces and tabs (right trim). There is no left trim to do: every leading space or tab is indentation (section 8).
- Compaction of spaces into a single one
The result of this normalization is the node name.
A node whose logical name is the empty string ("") is invalid and MUST cause a parse error.
Equivalent examples at the Node name level:
Node name:
Node name: value
Node name : value
Node name (@a.special.namespace):
Node name(a.normal.namespace):
Node name >>
Node name>>The definition of a node MUST always include either : (INLINE container node) or >> (BLOCK text node),
always preceded by a non-empty name.
4.2 Restrictions on the node name
The node name will only allow Unicode letters, digits and combining marks (general
categories L, Nd, Mn and Mc, in any script: Latin, Greek, Cyrillic, Arabic, Devanagari,
Thai, CJK…) and the separator characters -, _, . Names with diacritics, uppercase, and
lowercase letters are allowed. Combining marks (Mn, Mc) are the vowels and signs that many
scripts write on the base letter —हिंदी cannot be written without them— and the combining
accents with no precomposed form; enclosing marks (Me) are not allowed. The check is made on
the NFC form of the name (section 4.3), so e + combining accent is checked as é.
The name MUST contain at least one letter or digit: a name made up only of separators would have an empty canonical name (section 4.3).
4.3 Canonical node name
The canonical name is formed from the node name through the following process:
- Unicode NFC normalization (unifies the precomposed and decomposed forms of the same
character:
éas a single code point ande+ combining accent are equivalent) - Conversion to Unicode lowercase, independent of the locale. It is not full Unicode
case folding:
ßis preserved (Straße→straße) and is different fromss(STRASSE→strasse) - Compaction of spaces (not necessary on an already normalized name)
- Replacement of any sequence of separators (
-,_,) with a single hyphen (-) - Remove hyphens (
-) at the beginning and end if any exist
Diacritics and non-Latin letters are preserved: name equality is insensitive to case
and separators, but sensitive to accents and script, following the model of
internationalized domain names (IDN). Thus, Título and título are the same node,
but Caña and Cana, or Peña and Pena, are different nodes. Unlike node names, namespaces
remain restricted to ASCII [a-z0-9] (section 7).
The canonical name will be used to determine whether one node has the same name as another. It will also be used internally by all lookup or checking operations, to determine whether it is the same element.
A node whose canonical name turns out to be the empty string (e.g. ___) is invalid and
MUST cause a parse error, just like an empty logical name (section 4.1).
Examples of transformation:
A namé with äccent: a-namé-with-äccent
A NAMÉ WITH ÄCCENT: a-namé-with-äccent
SIZe number 2__ and 3: size-number-2-and-3
Пример 1: пример-1
Name 日本語: name-日本語
Straße: straße
STRASSE: strasse4.4 Style guidelines
The recommended style guidelines are as follows:
- Separate the name from the definition of a namespace with a single space
- Separate
:from the value with a single space :goes immediately after the name or the namespace if present>>has no character after it- Separate the node name or the namespace with a space before
>> - Do not use more than one space in names
Examples of correct style:
5. Container nodes, INLINE type
The : form defines an INLINE container node with the following characteristics:
- It may have a value (optional).
- It may have no value (empty node).
- It may have children (nested nodes).
- Its structured content includes:
- The node line itself.
- Its descendants with greater indentation.
Examples:
5.1 Value normalization
Values are literals: strong normalization applies only to structural identifiers. The inline value only gets a trim on both sides; the complete rule and its cases are in section 10.1.
6. Text block nodes, BLOCK type
The >> form defines a block of literal text.
Valid examples:
6.1 Formal rules
- The
>>node line MUST NOT contain significant content after>>, except optional spaces. - All lines with indentation strictly greater than that of the
>>node belong to the textual content of the block. - Within the block content (indentation strictly greater than that of the
>>node):- The parser MUST NOT interpret any line as a structured node, even if it contains
:or other STXT syntax. - The parser MUST NOT interpret lines beginning with
#as comments; they are literal text.
- The parser MUST NOT interpret any line as a structured node, even if it contains
- The block ends when a non-empty line appears whose indentation is less than or equal to the indentation of the
>>node, whether or not it is a comment. - A comment line (see section 9) with indentation less than or equal to that of the
>>node closes the block like any other non-empty line, and is then discarded as a comment. A block is a literal: it cannot be commented from inside. - Empty lines MUST NOT close the block, regardless of their indentation. Those that precede more block text are preserved as content; the final ones are discarded when the block closes (section 10.3).
- The comparison of indentations between the lines and the
>>node is done by level (section 8.1), regardless of the style (tabs or spaces) each line uses. - The block also ends upon reaching the end of the document.
- Content lines additionally follow the canonicalization rules of section 10.2.
As a consequence, the content of a >> block is always contiguous in the file: it runs
from the line after the >> node to the last non-empty line with strictly greater
indentation, with no comment able to interleave. See section 9.1.
Empty lines after that last line are not part of the content: they are the final empty
lines that section 10.3 discards.
6.2 Example
Block >>
Text
Child: value YES allowed, it is text, not parsed
Another child: YES allowed
# This is also text
Next Node: valueIn this example:
- Everything indented below
Block >>is literal text. Child: valueandAnother child: YES allowedare not nodes, but text.# This is also textis literal text, because its indentation is greater than that ofBlock >>.Next Node: valueis outside the>>block.
7. Namespaces
A namespace is optional and is specified like this:
Rules:
- A namespace MAY begin with
@. - It MUST use hierarchical format (
a.b.c), with at least 2 elements (a.b). - The effective namespace of a node that does not specify one is inherited from its parent, or is the empty namespace
""for a root node: see section 7.2. - The empty namespace cannot be specified as
Node name (). - There MUST NOT be spaces or tabs between the parentheses:
Node ( a.b ):andNode (a. b):are invalid (INVALID_NAMESPACE). They may appear between the name and(, and between)and:or>>. - A child node may redefine its namespace by indicating
(another.namespace), in which case it uses that namespace instead of the inherited one, and its descendants inherit the new one. - In the input, a namespace MAY be written with uppercase or lowercase ASCII letters; the parser MUST normalize it to lowercase. The behavior is analogous to domain names:
COM.DEMO.DOCSandcom.demo.docsare the same namespace. - Lowercase normalization occurs during parsing: the logical representation of the tree contains only the lowercase form. The original uppercase form is not preserved.
- By style rules, a namespace should be written directly in lowercase.
- The namespaces under
@stxt(@stxt.schema,@stxt.template, and any other@stxt.*) are reserved for the STXT language itself and its official specifications. An application MUST NOT define its own namespaces under@stxt.
Note: any ( appearing after the name of a node opens a namespace: parentheses
cannot form part of the name (section 4.2). For example, Quantity (kg): 3 is
invalid, because kg does not comply with the minimum a.b format of a namespace.
7.1 Restriction to ASCII
Each element of a namespace (Ident) MUST be formed only by characters
in the range [a-z0-9] (in its canonical form), with an optional @ at the start of the complete
namespace to indicate a special namespace.
In the input, uppercase ASCII letters [A-Z] are also accepted, which the parser
normalizes to lowercase. Diacritics, non-ASCII characters, and spaces are not accepted.
This ASCII restriction is deliberate: it avoids Unicode normalization ambiguities
and homographic attacks (visually identical but distinct characters, e.g. a Latin a
versus a Cyrillic а), consistently with the security priorities of
STXT (see section 15).
7.2 Inheritance and level 0 nodes
- The effective namespace of the root node (level 0) that does not specify a namespace is the empty namespace
"". - A child node without an explicit namespace inherits the effective namespace of its parent.
- There is no lateral inheritance between level 0 nodes: each root node without an explicit namespace has namespace
"", independently of the namespace of any previous root node.
Example:
In this example, Appendix has namespace "" (empty). It does not inherit com.example.docs
from the previous root node. The absence of lateral inheritance guarantees that the meaning of
a root node does not depend on the nodes that precede it (see section 8.5, concatenation).
8. Indentation and Hierarchy
Indentation defines the structured hierarchy of the document.
8.1 Allowed indentation
An STXT document:
- MAY use spaces or tabs for indentation.
- The indentation of a line MUST be homogeneous: either only tabs, or only spaces.
- Mixing spaces and tabs in the indentation of the same line is a parse error (see section 8.3).
- With tabs, each tab is exactly 1 level.
- With spaces, it MUST use multiples of 4 spaces: each group of 4 spaces is 1 level.
- Different lines of the same document MAY use different styles (some tabs, some spaces): the hierarchy is compared by level, not by columns. This is not recommended as a matter of style, and a parser MAY emit a warning if a document combines both styles.
Why it is so strict (non-normative). The level of a line is computed by looking at that line alone: count its tabs, or divide its spaces by four. It is not necessary to remember how the previous lines were indented, nor to keep a stack of levels as in variable-indentation languages. That is why there are no other widths (2 spaces, for instance) and why mixing is not allowed: a line that mixes tabs and spaces looks different in every editor depending on the tab width, and STXT prefers to reject it rather than interpret it. Whoever wants a more compact indentation can use tabs and adjust the width in the editor: the visual preference belongs to the editor, not to the language.
8.2 Indentation examples
In the following examples, . is shown to identify a space, and |--> to identify a tab.
The tab is represented with a width of 4 columns, as a text editor configured to 4 would do.
Example with tabs:
Level 0 node: Level 0 value
|-->Level 1 node:
|-->Another level 1 node:
|-->|-->Level 2:
|-->|-->Level 2:
|-->Level 1:
|-->Level 1:
Example with spaces:
Level 0 node: Level 0 value
....Level 1 node:
....Another level 1 node:
........Level 2:
........Level 2:
....Level 1:
....Level 1:
Example with different styles on different lines.
Allowed: each line uses a homogeneous indentation (only tabs or only spaces), even though the document combines both styles. It is not recommended, and a parser MAY give a style warning. This example has the same hierarchy as the previous two.
Level 0 node: Level 0 value
|-->Level 1 node: 1 TAB: level 1
....Another level 1 node: 4 spaces: level 1
|-->|-->Level 2: 2 TABs: level 2
........Level 2: 8 spaces: level 2
....Level 1: 4 spaces: level 1
|-->Level 1: 1 TAB: level 1
8.3 Level errors
A parser MUST give a parse error in the following cases:
- Non-consecutive levels:
Level 0:
....Level 1:
............Level3: ERROR, you cannot go from level 1 to level 3
- An indented first line. A level 0 node has no parent, and before the first node there is no reference node: the reference level is −1, so the first node —or the first comment— of the document MUST be at level 0. The same holds after every node has been closed: a level 0 node may always follow any other.
....Level 1: ERROR, the first node of the document must be at level 0
- Not reaching a multiple of 4 when using spaces
- Mixing spaces and tabs in the indentation of the same line
Level 0:
....Level 1:
...Almost level 1: ERROR: 3 spaces (does not reach 4)
Level 0:
....Level 1:
.|-->Mixed level: ERROR: mix of space and TAB on the same line
Level 0:
....Level 1:
..........More than level 2: ERROR: 10 spaces (not a multiple of 4)
Level 0:
|-->Level 1:
|-->....Mixed level: ERROR: mix of TAB and spaces on the same line
Note: these level rules apply to lines that define nodes and also to comment lines
(see section 9): the indentation of a comment MUST be valid and its level
MUST NOT exceed that of the last node by more than one, even though the comment is not
part of the hierarchy. Empty lines are exempt. Text lines of a >> block follow the rules
of section 10.2: only their block-level prefix (the level of the >> node plus
one) must be valid; the rest of the line is free text.
8.4 Hierarchy
- Indentation MUST increase consecutively (jumps are not allowed).
- Child nodes MUST have greater indentation than their parent.
- Indentation within a
>>block does not affect structural hierarchy: it is simply text. - The tree resulting from parsing MUST preserve the order of appearance of sibling nodes as they appear in the document. A conforming implementation MUST NOT reorder the children of a node.
8.5 Multiple level 0 nodes and concatenation
An STXT document MAY contain multiple level 0 nodes (root nodes). There is no obligation for a single root node. How those root nodes are interpreted or used belongs to the application, not to the STXT core.
It MAY also contain none: an empty document, or one made only of comments and empty lines, is valid and its tree is the empty sequence of root nodes.
Example of a valid document with three root nodes:
Closure under concatenation. As a direct consequence of allowing multiple level 0 nodes and the absence of lateral inheritance (section 7.2), the concatenation of two valid STXT documents is also a valid STXT document, provided that the second begins on a level 0 line (which occurs by definition, since its root nodes are at level 0).
This allows, without additional syntax, use cases such as:
- Log or record files in append mode (add at the end).
- Streaming of successive records.
- Combining files with a simple text concatenation (
cat a.stxt b.stxt > c.stxt).
The property is stated over lines, and concatenating files has two byte-level
preconditions: the first file MUST end in a line break (otherwise its last line and
the first line of the second merge into one), and the second MUST NOT begin with a
BOM (a U+FEFF that is not at the start of the document is not a BOM but content,
section 3, and would make the name of the second file's first node invalid).
STXT does not need a derived format for "lists of documents": a document already is a sequence of root nodes.
9. Comments
Outside the content of a >> block, a line is a comment if, after its indentation,
the first character is #.
General rules for comments:
- A comment is completely discarded: it is not part of the resulting tree.
- The indentation of a comment is validated like that of a node (section 8): only tabs or only groups of 4 spaces, never mixed on the same line, and its level MUST NOT exceed by more than one the level of the last node read (the same limit a node would have in that position). A comment that breaks these rules is a parse error (section 11), with the same codes as a node.
- A comment does not alter the hierarchy: it does not change the reference level for the following lines. The node after a comment is validated against the last node, not against the comment.
- A comment closes an active
>>block like any other non-empty line with indentation less than or equal to that of the>>node; within the content of a block there are no comments (see 9.1).
Example:
Examples of invalid comments:
Node:
# ERROR: 3 spaces (4 are not reached)
Node:
........# ERROR: level 2 after a level 0 node
Node:
|-->....# ERROR: mix of TAB and spaces on the same line
The rule follows from visual coherence: indentation is the structure of the document, and a comment placed outside it gives a wrong indication of which node it refers to.
9.1 Comments and >> blocks
The block rules are in section 6.1; this section only details the case of comments.
Within a >> block, two situations must be distinguished according to the indentation of the line:
- Indentation strictly greater than that of the
>>node: the line is literal text of the block, even if it begins with#. It is not a comment. - Indentation less than or equal to that of the
>>node: the line closes the block, whether it is a comment or a node. If it is a comment, it is then discarded like any other comment; closing the block is all it does, it does not affect the rest of the hierarchy.
A block is a literal, like a string or a heredoc: it cannot be commented from inside, and its content is always contiguous.
Example:
Inline node:
Text node >>
# It is NOT a comment: it is text of the block
Text 1
# It is a comment: it closes the block
Text 2
Another node: It is now another node
Details:
# It is NOT a commentis more indented thanText node >>, so it is text (and its#is preserved).# It is a commenthas the same indentation asText node >>: it closes the block, whose content is left with two lines (# It is NOT a comment…andText 1), and is discarded.Text 2no longer has a block to belong to: the parser processes it as a node and the document is invalid (indentation level jump, section 11). This is deliberate: a text line that begins with#and is de-indented by mistake does not vanish silently; it makes the next text line fail.- Without the
Text 2line, the document is valid andAnother nodeis a sibling ofText node.
9.2 Style for comments
- It is recommended that the comment be at the same level as the following node. That is, comments for the following node. Section 9 only bounds the level (at most that of the last node plus one); within that margin, this is the style.
- There are no comments inside a text block: a
#line more indented than the>>node is text, and one with indentation less than or equal closes the block. If a text line that begins with#is de-indented by mistake, the block closes early and the next text line, if any, produces a parse error. Only if it was the last line of the block does the error go unnoticed, because it is indistinguishable from a legitimate comment after the block: it is the only case in which an indentation error does not fail noisily.
10. Whitespace normalization
This section defines how whitespace must be normalized to
ensure that different implementations produce the same logical representation
from the same STXT text. In everything that follows, "spaces and tabs" are exactly
the two blanks defined in section 4: U+0020 and U+0009.
10.1 Inline values
When parsing a node with ::
-
The parser takes all characters from immediately after
:to the end of the line. -
The inline value MUST be normalized by applying:
- Removal of leading spaces and tabs (left trim).
- Removal of trailing spaces and tabs (right trim).
This implies that the following lines are equivalent at the parsing level:
In all cases, the logical value of the Name node is "Joan".
If after the trim the value is empty, the inline value is considered the empty string ("").
10.2 Lines within >> blocks
The block level of a >> node is fixed: the level of the >> node plus one.
It is not derived from the content: no line of the block (not even the first one)
establishes a minimum indentation for the others. For each non-empty line belonging
to the block (level strictly greater than that of the >> node):
- The prefix of the line that covers the block level MUST be homogeneous (only tabs
or only spaces, section 8.1). The calculation is by level, so each line may use a
style different from that of the
>>node line or the rest of the block. A non-empty line whose prefix does not reach the block level with a valid number of spaces (e.g. 2 spaces under a level 0>>) is the error of section 8.3 (INVALID_NUMBER_SPACES): it is neither block text nor a block close. - The parser removes only that prefix (the block level), preserving any additional indentation as part of the text. The rest of the line is free text: once the block level is reached, no further indentation or character rules apply — it may contain spaces and tabs in any combination, and any STXT syntax without being interpreted.
- On that content, the parser MUST remove all trailing spaces and tabs (right trim).
- Empty lines that precede more content are preserved; the final empty lines of the block are discarded when it closes (see section 10.3).
Example of line canonicalization:
Logical representation of the block content:
- Line 1:
"Hello" - Line 2:
" World"(the 4 additional spaces after the block level are preserved; trailing spaces are removed)
Since the block level does not depend on the content, the first line may be more indented than later lines of the same block:
Logical representation of the block content:
- Line 1:
" Deeply indented" - Line 2:
"Less indented"
10.3 Empty lines in >> blocks
- Leading and intermediate empty lines — those that precede more non-empty content of
the block — MUST be preserved as empty lines (
"") in the logical representation of the text. - Final empty lines — the sequence of empty lines after the last non-empty line of the block — MUST be discarded when the block closes: they are not part of the logical representation. A block composed only of empty lines has the empty sequence of lines as its content, just like a block with no lines.
- The rule is semantic, not syntactic: an empty line never closes the block (section 6.1). The discard happens when the block closes, by a non-empty line at a level less than or equal to the block node's, or by the end of the document.
The reason is the same as for the inline value trim (section 10.1): what cannot be seen must not change the meaning. An empty line between the end of a block and the next node is visual separation of the document, not content, and the number of empty lines at the end of a file is often decided by the editor, not the author: two visually identical documents MUST produce the same tree. A block thus keeps its left edge (the additional indentation, section 10.2) and its top edge (the leading empty lines), which are deliberate and visible, and trims its right edge (per-line trim) and its bottom edge (the final empty lines).
Example:
Logical content of the Text block:
- Line 1:
""(leading: preserved) - Line 2:
"Line 1" - Line 3:
""(intermediate: preserved) - Line 4:
"Line 2"
The empty line after Line 2 is final: it is discarded when the line Next: node
closes the block, and the result would have been the same if the document ended there.
The logical representation has exactly four lines.
11. Error Rules
A document is invalid if any of these conditions occurs:
- Spaces that are not a multiple of 4 (when spaces are used for indentation).
- A mix of spaces and tabs in the indentation of the same line (section 8.1).
- Jumps in indentation levels, including an indented first line (section 8.3).
- A
>>node contains significant inline content on the same line as>>. - A node contains neither
:nor>>. - The logical name of a node is the empty string.
- The canonical name of a node is the empty string (section 4.3).
- The name of a node contains disallowed characters (section 4.2).
- A namespace does not comply with the restrictions of section 7 (format
a.b, ASCII only[a-z0-9]per element, optional initial@).
Conditions 1, 2 and 3 also apply to comment lines (section 9). Empty lines are not a cause of error (their indentation is not validated).
A conforming parser MUST reject the document.
11.1 Error codes
Every error carries a stable code, in uppercase English, which is the same in every conforming implementation: a program that filters or counts errors does so by code, not by the message text, which each implementation words (and translates) at its own discretion. These are the codes of the syntax errors; each one points to the condition of the list above. The codes do not change: renaming one would be an incompatible change, which the status of this specification (§1.1) rules out.
| Code | Condition |
|---|---|
INDENTATION_SPACES_NOT_VALID |
1: indentation spaces that are not a multiple of 4 |
INDENTATION_MIXED |
2: tabs and spaces mixed in the indentation of one line |
INDENTATION_LEVEL_NOT_VALID |
3: a jump of more than one level from the last node, or an indented first line |
BLOCK_VALUE_NOT_ALLOWED |
4: content after >> on the node line |
INVALID_LINE |
5 and 6: the line has neither : nor >>, the >> precedes the :, or the name is the empty string |
INVALID_NODE_NAME |
7 and 8: empty canonical name, or characters not allowed |
INVALID_NAMESPACE |
9: malformed namespace |
UNEXPECTED_ERROR |
an unforeseen exception of the implementation, wrapped with its line; a document conforming to this specification never produces it |
The three INDENTATION_* codes apply equally to comment lines (section 9). The
codes of the parser limits are defined in section 11.2. The
codes of semantic validation are defined in STXT-SCHEMA-SPEC and
STXT-TEMPLATE-SPEC; those of schema discovery, in
STXT-DISCOVERY-SPEC.
11.2 Parser limits
A parser SHOULD apply limits to the input that bound memory and processing time against hostile or runaway documents: they are the last line of defense when the input is not trusted. The concrete values are deliberately arbitrary and this specification does not impose them; the three limits below, with the default values of the official implementations, are the recommended configuration:
| Limit | Recommended default | Exceeded when… |
|---|---|---|
| Nesting depth | 100 levels |
a line opens a node at level 100 or deeper (level 0 is the first: at most 100 levels are open) |
| Line length | 10 000 characters |
one input line, indentation included, exceeds that length |
| Input size | 10 000 000 characters |
the total input consumed exceeds that size |
What matters is not the numbers but the contract, and that one is normative. A parser
that applies limits MUST let the program using it configure them —the value -1
disables the corresponding limit— and MUST use the error codes of the table below,
the same in every implementation. Lengths are measured in the natural units of the
platform's string representation (code points or UTF-16 units); for ASCII content they all
coincide. The input size limit is checked as the input is consumed, compatibly with
streaming parsing (section 15).
| Code | Limit |
|---|---|
LIMIT_NESTING_EXCEEDED |
nesting depth |
LIMIT_LINE_LENGTH_EXCEEDED |
line length |
LIMIT_INPUT_SIZE_EXCEEDED |
input size |
A limit error MUST abort the parse: the error is emitted and no further input is processed. This also holds for the modes that collect several errors and continue: keeping on processing an input that has already exceeded a limit would void the protection the limit provides, so the limit error is in every case the last one.
Exceeding a limit does not make the document invalid in the sense of section 11: the same document may parse under higher limits. The limit error reports that the parser stopped, not that the document breaks the syntax.
Note (non-normative). The nesting-depth limit also bounds the operations that walk the
tree recursively after parsing — semantic validation and writing
(STXT-TREE-SPEC) —, which have no other bound than this. Disabling it
(-1) leaves them without stack protection against a very deep tree, on top of relaxing
the parse.
12. Conformance
An STXT implementation is conforming if:
- It implements the syntax described in this document.
- It accepts UTF-8 with or without BOM and both LF and CRLF line endings (section 3).
- It applies the strict indentation and hierarchy rules.
- It correctly interprets nodes with
:and>>blocks. - It interprets comments outside the content of
>>blocks, validates their indentation like that of a node without letting them alter the hierarchy, and a comment with indentation less than or equal to that of the>>node closes the active block, according to section 9. - It treats everything within the content of a
>>block (strictly greater indentation) as literal text. - It accepts multiple level 0 nodes —and also none (section 8.5)— and preserves the order of appearance of sibling nodes.
- It does not apply lateral namespace inheritance between level 0 nodes.
- It normalizes namespaces to lowercase during parsing.
- It applies the whitespace normalization rules of section 10.
- It rejects invalid documents according to section 11.
- If it applies parser limits, it follows the contract of section 11.2: configurable,
LIMIT_*codes, and the parse aborted when one is exceeded.
The interoperable representation of the logical tree resulting from this parsing is defined by STXT-TREE-SPEC. Emitting it as JSON is an optional capability of a base parser; it does not alter the syntax rules in this document.
Implementation blueprint (non-normative). The language-neutral stxt-impl
pseudocode turns these rules into shared algorithms and contracts for language ports.
It is the second authority after the specifications, but it never replaces or can
contradict them. Its public repository is
github.com/stxt-lang/stxt-impl.
13. File Extension and Media Type
13.1 File Extension
STXT documents SHOULD use the extension: .stxt
13.2 Media Type (MIME)
- Media type:
text/stxt. Its registration in the IANA standards tree through an RFC is planned; until then it is not registered. - Compatible alternative:
text/plain; charset=utf-8 - The content is always UTF-8 (section 3); the
charsetparameter, if present, MUST beutf-8.
14. Normative Examples
14.1 Valid document
Document (com.example.docs):
Author: Joan
Date: 2025-12-03
Summary >>
This is a text block.
With several lines.
Config:
Mode: Active14.2 Block with empty lines
Logical content of the Text block:
"""Line 2"
The leading empty line is preserved; the final one is discarded (section 10.3).
Next is a second root node, outside the block.
14.3 Comments inside and outside blocks
The Body block contains two lines: "# This is text" and "More text". The line
# This is a comment, with indentation less than or equal to Body >>, closes the block and
is discarded as a comment.
14.4 Multiple root nodes
Valid document with two Entry root nodes at the same level. This allows, for example,
a log-type record through simple append.
15. Security Considerations
STXT has been designed with parsing security as a fundamental priority, minimizing the attack surface compared to other structured textual formats.
A conforming STXT parser is inherently resistant to common classes of vulnerabilities:
- Immune to entity expansion attacks (such as "billion laughs" or XXE): the language does not define entities, external references, or inclusion of remote resources.
- Immune to arbitrary code execution: there are no dynamic features, custom tags, loaders, or object deserialization. The only resulting structure is a simple tree of nodes and textual values.
- Immune to injection inside literal blocks: all content within a
>>node is treated as literal text without any interpretation, even if it contains:,>>,#, or other STXT syntax. - No identifier ambiguity: namespaces are restricted to ASCII (section 7.1), eliminating homographic attacks based on visually equivalent Unicode characters.
- Low risk of denial of service: the strict rules of consecutive indentation and the absence of circular references or anchors limit structural complexity. In addition, section 11.2 recommends configurable limits —nesting depth, line length and input size—, which the official implementations enable by default.
- Streaming parsing with bounded memory: thanks to allowing multiple level 0 nodes and the absence of backward references, a parser MAY emit each complete root tree as soon as it detects the start of the next level 0 node, retaining only the tree of the root in progress —which it emits and discards before starting the next. Memory usage is then on the order of the largest root tree, not the total document size. This makes it feasible to process very large files made of many roots (logs, streams) safely; a single giant root is retained whole, and to bound that case too are the limits of section 11.2.
- Optional external schemas: semantic validation is a separate layer. A basic parser MAY operate without loading external schemas, eliminating risks associated with their resolution.
Consequently, STXT is especially suitable for processing documents from untrusted sources (remote configurations, user input, data exchange) where parser security is critical.
Beyond the format itself, the usual precautions for any text apply: values and blocks admit any character, including bidirectional controls and other invisible characters that may render misleadingly in a viewer (node names do not, section 4.2: the format and control categories are not allowed). The application that displays or interprets a value is responsible for the meaning it assigns to it.
Implementations MUST reject invalid documents according to section 11 and MUST NOT introduce extensions that allow external loading or dynamic evaluation without explicit security measures.
16. Appendix A — Grammar (Informal)
Document = { Line }
Line = Comment | EmptyLine | BlockTextLine | Node
Comment = Indentation "#" { any character until end of line }
; outside block content; discarded; its indentation is validated like a node's
; (level <= last node + 1) but does not alter the hierarchy (section 9)
EmptyLine = { Blank } ; ignored outside blocks; inside, a "" line of the content
; if it precedes more text; the block's final ones are discarded (section 10.3)
BlockTextLine = BlockIndentation FreeText ; only with an open >> block: homogeneous prefix of level(>> node) + 1;
; the rest is literal text, right-trimmed (section 10.2)
Node = Indentation Name { Blank } [ Namespace { Blank } ] ( Inline | BlockStart )
Inline = ":" InlineText ; InlineText = the rest of the line, trimmed on both sides; may be empty (section 10.1)
BlockStart = ">>" { Blank } ; nothing significant after >> (section 6.1)
Namespace = "(" ["@"] Ident { "." Ident } ")" ; at least 2 Ident; no blanks inside the parentheses (section 7)
Ident = [A-Za-z0-9]+ ; accepted in input; the parser MUST normalize it to lowercase (section 7);
; canonical form (and recommended by style): [a-z0-9]+
Name = characters \p{L} | \p{Nd} | \p{Mn} | \p{Mc} | "-" | "_" | " ", on the NFC form, with at
least one letter or digit (section 4.2); the logical name is the text right-trimmed
with its spaces compacted (section 4.1)
Indentation = { TAB } | { " " } ; only tabs or only groups of 4 spaces (section 8):
; 1 tab = 1 level, 4 spaces = 1 level; mixing on the same line,
; or a number of spaces that is not a multiple of 4, is a parse error
BlockIndentation = Indentation ; of exactly level(>> node) + 1; whatever follows is FreeText
FreeText = { any character until end of line }
Blank = " " | TAB ; U+0020 or U+0009
Notes for implementers:
-
The parser must process the document line by line, maintaining state of:
- Current indentation level of the parent node.
- State of the active
>>block (if any). Its block level is not additional state: it is always the level of the>>node plus one. - Current inherited namespace.
-
Basic parsing flow:
- Read line (discarding a trailing
\rif present, section 3) and calculate its indentation level (according to section 8 rules). - If there is an active
>>block:- If the line is empty → add an empty line (
"") to the block. - If indentation > indentation of the
>>node → strip the block level (level of the>>node + 1) and add the rest as literal text (right trim). - If indentation ≤ indentation of the
>>node, whether or not it is a comment → close block and process the line outside the block. - When a block closes —also upon reaching the end of the document— remove the final empty lines from its content (section 10.3).
- If the line is empty → add an empty line (
- If there is no active block:
- Empty line → ignore (does not affect hierarchy).
- Starts with
#(after indentation) → comment; validate its indentation like a node's (homogeneous style, multiple of 4, level ≤ last node + 1) and discard without touching the hierarchy. - Otherwise → new node (normalize name, detect namespace, type : or >>).
- Read line (discarding a trailing
-
Namespace inheritance:
- The effective namespace of the root node is empty by default.
- There is no lateral inheritance between level 0 nodes.
- Each child node without an explicit namespace inherits the effective namespace of its parent.
- If a node defines its own namespace within
(), this replaces the inherited one for it and all its descendants.
-
Additional normalization:
- Node names: according to section 4.1–4.3.
- Namespaces: normalized to lowercase during parsing (section 7); only the lowercase form is preserved.
- Inline values: left and right trim (section 10.1).
- Block lines: strip the block level preserving any additional indentation + right trim + preserve the empty lines that precede more content and discard the final ones (section 10.2–10.3).
17. Appendix B — Interaction with @stxt.schema
The schema system allows adding semantic validation to STXT documents without modifying the base syntax of the language.
The STXT core does not define how an implementation should react: the behavior belongs exclusively to the schema system (STXT-SCHEMA-SPEC).
A schema is an STXT document whose namespace is: @stxt.schema
and whose purpose is to define the structural rules, value types, and cardinalities of the nodes belonging to a specific namespace.
The STXT core does not interpret these rules; it only defines how they are expressed and how they are combined through namespaces.
The location of schema documents on the file system (.stxt directories) is defined in STXT-DISCOVERY-SPEC.
17.1. Associating a schema with a namespace
To associate a schema with the namespace com.example.mail, a document is written (it is the
same model the template of appendix C expresses):
Schema (@stxt.schema): com.example.mail
Node: Email
Children:
Child: From
Min: 1
Max: 1
Child: To
Min: 1
Max: 1
Child: Cc
Max: 1
Child: Bcc
Max: 1
Child: Title
Max: 1
Child: Body Content
Min: 1
Max: 1
Child: Metadata (org.example.meta)
Max: 1
Node: From
Type: EMAIL
Node: To
Type: EMAIL
Node: Cc
Type: EMAIL
Node: Bcc
Type: EMAIL
Node: Title
Node: Body Content
Type: TEXT17.2. Application to STXT documents
A document that declares the same namespace:
Email (com.example.mail):
From: [email protected]
To: [email protected]
Title: Project report
Body Content >>
Hello Mery!
The book is finished!can be validated by an implementation that supports STXT schemas:
- Validating the presence of nodes according to
Nodein the schema. - Validating value types (
TEXT,DATE,NUMBER, etc.). - Validating cardinalities defined in
Child.
17.3. Independence of the core
STXT MUST NOT impose semantic rules coming from schemas. The schema system is a separate and optional component that operates on the already parsed STXT.
It MAY also act as part of the parsing process. In that case it SHOULD be weakly coupled to it. This would make it possible to detect errors without having to wait until the end of parsing.
18. Appendix C — Interaction with @stxt.template
The template system allows adding semantic validation to STXT documents without modifying the base syntax of the language.
The STXT core does not define how an implementation should react: the behavior belongs exclusively to the template system (STXT-TEMPLATE-SPEC).
A template is an STXT document whose namespace is: @stxt.template
and whose purpose is to define the structural rules, value types, and cardinalities of the nodes belonging to a specific namespace.
The template system is analogous to schemas, but with a simplified syntax, oriented toward rapid prototypes. Even so, it is a valid system for all kinds of documents. It could be considered syntactic sugar, since internally it can use the same representation as a schema.
The template system MAY coexist alongside a system with schemas, since in the end a template defines the same information as a schema.
18.1. Associating a template with a namespace
To associate a template with the namespace com.example.mail, a document is written:
Template (@stxt.template): com.example.mail
Structure >>
Email (com.example.mail):
From: (1) EMAIL
To: (1) EMAIL
Cc: (?) EMAIL
Bcc: (?) EMAIL
Title: (?)
Body Content: (1) TEXT
Metadata (org.example.meta): (?)Once defined, a template fulfills the same function as a schema. If an implementation finds several schemas or templates applicable to the same namespace, it SHOULD define a clear and deterministic priority policy. For a specific validation, a single effective semantic source MUST be selected: either a schema or a template. When definitions are discovered on the file system, that policy is fixed by STXT-DISCOVERY-SPEC.