STXT Core

Status:
Zenith
last modified:
2026-09-07

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.schema or @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 modif is 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.
  • Status says 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+FEFF before 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:

  1. Inline container node (INLINE node): Node name: Inline value
  2. 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:

Node 1: Inline value
	Node 2 without value:
	Node 3 with another value: this is the other value

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 applied

A node may optionally include a namespace:

Name (normal.namespace):
Name (@special.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 >>,

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 and e + combining accent are equivalent)
  • Conversion to Unicode lowercase, independent of the locale. It is not full Unicode case folding: ß is preserved (Straßestraße) and is different from ss (STRASSEstrasse)
  • 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: strasse

4.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:

Name with value: The value
Name without value:
Name with namespace (the.namespace):
Text node >>

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:

Title: Report
Author: Joan
Node:
Node: Value
Node:
    SubNode 1: 123
    Another subnode: 456

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:

Description >>
    Line 1
    Line 2
Section>>
    Accepts the operator without a space

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 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: value

In this example:

  • Everything indented below Block >> is literal text.
  • Child: value and Another child: YES allowed are not nodes, but text.
  • # This is also text is literal text, because its indentation is greater than that of Block >>.
  • Next Node: value is outside the >> block.

7. Namespaces

A namespace is optional and is specified like this:

Node (com.example.docs):
Another node (another.namespace):
More nodes (@a.special.name):

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 ): and Node (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.DOCS and com.demo.docs are 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:

Document (com.example.docs):
    Author: Joan
Appendix:
    Note: text

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:

Document 1: First
Document 2: Second
Document 3 >>
    Text of the third

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:

# Root comment
Node:
    # Inner comment
    Child: value
# Closing comment, at any level from 0 to 2

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 comment is more indented than Text node >>, so it is text (and its # is preserved).
  • # It is a comment has the same indentation as Text node >>: it closes the block, whose content is left with two lines (# It is NOT a comment… and Text 1), and is discarded.
  • Text 2 no 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 2 line, the document is valid and Another node is a sibling of Text 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 ::

  1. The parser takes all characters from immediately after : to the end of the line.

  2. 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:

Name: Joan
Name:     Joan
Name: Joan
Name:     Joan

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):

  1. 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.
  2. 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.
  3. On that content, the parser MUST remove all trailing spaces and tabs (right trim).
  4. 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:

Block >>
    Hello
        World

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:

Block >>
        Deeply indented
    Less indented

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:

Text >>

    Line 1

    Line 2

Next: node

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:

  1. Spaces that are not a multiple of 4 (when spaces are used for indentation).
  2. A mix of spaces and tabs in the indentation of the same line (section 8.1).
  3. Jumps in indentation levels, including an indented first line (section 8.3).
  4. A >> node contains significant inline content on the same line as >>.
  5. A node contains neither : nor >>.
  6. The logical name of a node is the empty string.
  7. The canonical name of a node is the empty string (section 4.3).
  8. The name of a node contains disallowed characters (section 4.2).
  9. 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 charset parameter, if present, MUST be utf-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: Active

14.2 Block with empty lines

Text>>

    Line 2

Next:

Logical content of the Text block:

  1. ""
  2. "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

Document:
    Body >>
        # This is text
        More text
    # This is a comment

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

Entry:
    Date: 2026-06-13
    Text: first
Entry:
    Date: 2026-06-13
    Text: second

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:

    1. Read line (discarding a trailing \r if present, section 3) and calculate its indentation level (according to section 8 rules).
    2. 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).
    3. 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 >>).
  • 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.14.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.210.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: TEXT

17.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 Node in 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.