The command line

stxt is the official STXT command: it validates, formats and describes documents from a terminal, a Makefile or a continuous-integration job. It is an interface over the TypeScript library @stxt-lang/core —it carries no parser of its own—, so it reports the same errors, with the same codes, as the VS Code extension, the playground and the Java and Python libraries. It is also the only command line in the ecosystem: the libraries do not include one.

The use of the commands on a complete project, with its .stxt/ directory and VS Code, is described in The working environment; the other tools, in Tools.

Installation

It is published on npm as @stxt-lang/cli and needs Node 20 or newer:

npm install -g @stxt-lang/cli
stxt --version

stxt 1.0.2 (@stxt-lang/core 1.0.2, spec 2026-09-07)

--version prints two versions and a date: the command's version, that of the parser it embeds, and the date of the STXT specification the parser implements (the STXT-SPEC date pinned by the conformance kit). The first two state what is installed; the date, which specification it conforms to: two installations with different packages read the same STXT as long as the spec date matches. Without a permanent installation, every command works through npx:

npx @stxt-lang/cli validate --recursive docs/

To update, npm update -g @stxt-lang/cli; to remove it, npm uninstall -g @stxt-lang/cli. From the package's 1.0 on, the commands, their options, the exit codes and --format json are stable within its 1.x line; the human-facing output is not (see Stability and versions). The version number and the release notes are in the repository.

Synopsis

stxt [--version | --help]
stxt validate <file|dir|->... [--recursive] [--format text|json] [--warn-schema | --no-schema]
              [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt format   <file|dir|->... [--recursive] [--tabs | --spaces] [--write | --check] [--clean]
              [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt describe <file|-> [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt schemas  [path]
stxt install  <file> [--local | --user | --system | --root <dir>] [--force] [--ignore-non-definitions]
Command What it does
stxt validate Parses and validates documents against the grammars it resolves; no output when all pass
stxt format Rewrites documents in canonical form, keeping comments
stxt describe Emits the logical tree of a document as JSON (STXT-TREE-SPEC)
stxt schemas Shows the resolution chain and which grammar applies to each namespace
stxt install Validates a grammar and installs it into a level of the resolution chain

Interface conventions, common to every command:

  • Every option has a single long form, with a double dash (--recursive). Only four have a short alias, the usual Unix conventions: -v (--version), -h (--help), -r (--recursive) and -w (--write).
  • --version and --help are honoured in any position and take precedence over the rest: stxt validate --help prints the general help (there is no per-command help).
  • An unknown option —single-dash ones included— or a non-existent command are usage errors: they exit with code 2 without performing any action.
  • Results (findings, JSON, formatted documents, listings) go to standard output; usage and read errors go to standard error, and so do the syntax errors of format and describe (the report of validate is the result, and goes to standard output).
  • - denotes standard input in validate, format and describe, following the Unix convention: a document arriving through a pipe is processed like a file and reported as <stdin>. It can be given only once. Without an argument, no command reads standard input: that is a usage error.
  • No command rewrites files without an explicit option: format only writes with --write, and install only overwrites with --force.

Exit codes

The contract is the same for every command: a CI job can tell a document failure from a misuse of the command.

Code Meaning
0 Success: the documents parse and validate, nothing needs reformatting, the grammar was installed…
1 Document failure: syntax or grammar errors, files that --check would change, an unreadable file, a definition that cannot be installed
2 Misuse: unknown command or option, missing argument, incompatible options, a directory without --recursive

stxt validate docs/ && make deploy runs the deployment only if the documents validate; a 2 in CI points at an error in the script, not in the documents.

stxt validate

stxt validate <file|dir|->... [--recursive] [--format text|json] [--warn-schema | --no-schema]
              [--max-nesting N] [--max-line-length N] [--max-input-size N]

For each document: it parses it, resolves its grammar chain —the .stxt/ directories of its folder and of all its ancestors, then ~/.stxt and /etc/stxt, or whatever STXT_PATH says; see Grammar resolution, below— and validates it against the grammar of every namespace it uses. When everything passes it writes nothing and exits with 0.

Option Effect
--recursive, -r Descends into directories and validates all their *.stxt, sorted by name; .stxt/ directories are skipped
--format text One finding per line, plus a summary (default)
--format json The same findings as a JSON array, for automated processing
--warn-schema Grammar errors are reported as warnings and do not fail; syntax errors still do
--no-schema Syntax only: no grammar is resolved or applied
--max-nesting N, --max-line-length N, --max-input-size N The parser limits (STXT-SPEC §11.2): nesting levels, line length and total input size; defaults 100, 10000 and 10000000; -1 disables the limit

The parser limits protect against runaway inputs, and sometimes they must be raised knowingly: an event log in STXT easily exceeds the default input size. stxt validate log.stxt --max-input-size -1 validates it whole. An exceeded limit is reported like any other parse error and aborts the parse of that document, so it is always its last finding. validate also parses in streaming: it reads the file in chunks and releases each root node once validated, so the memory it uses is on the order of one root node, not of the document — validating a log larger than memory works.

A directory as argument requires --recursive; without it, it is a usage error (2), which prevents validating a directory tree by accident. Files, directories and - can be mixed in the same call.

With - the document is read from standard input and called <stdin> in the findings. Since it lives in no directory, its grammar chain is that of the current directory, as if it were a file in that directory (the same chain stxt schemas shows without an argument); run from the project root, it validates against the same grammars as its files. The following example validates the version committed in git of the document of the next section, with the same two findings:

git show HEAD:docs/book.stxt | stxt validate -

<stdin>:6: [INVALID_VALUE] Published: Invalid date (1 de octubre de 2025) (error)
<stdin>:1: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.book:isbn' and min is 1 (error)
2 error(s), 0 warning(s)

The findings

With the project of The working environment —the com.acme.book template in .stxt/ and docs/book.stxt—, a document with an invalid date and without the mandatory ISBN produces:

stxt validate docs/book.stxt

/home/ana/libros/docs/book.stxt:6: [INVALID_VALUE] Published: Invalid date (1 de octubre de 2025) (error)
/home/ana/libros/docs/book.stxt:1: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.book:isbn' and min is 1 (error)
2 error(s), 0 warning(s)

Each line is file:line: [CODE] message (severity), with the absolute path of the file, and a count at the end. The code is stable, upper-case and the same in every tool of the ecosystem, so it can be used to filter in a script or to search the specifications. The most common ones:

Code Scope
INDENTATION_MIXED, INDENTATION_LEVEL_NOT_VALID, INVALID_LINE Syntax (STXT-SPEC)
INVALID_VALUE, TOO_FEW_CHILDREN, CHILD_NOT_DECLARED, NODE_NOT_DEFINED_IN_SCHEMA Grammar (STXT-SCHEMA-SPEC)
SCHEMA_NOT_FOUND The document uses a namespace the chain does not define
DISCOVERY_DUPLICATE_NAMESPACE, DISCOVERY_NOT_A_DEFINITION The resolution chain itself (STXT-DISCOVERY-SPEC)
FILE_NOT_READABLE The file does not exist or cannot be read

A cardinality error is reported on the line of the parent (Book, line 1), because it is the parent that lacks the ISBN. Errors of the resolution chain itself —an invalid grammar in .stxt/, two definitions of the same namespace at the same level— are reported as findings on line 0 of the offending file, not of the document, and they fail the validation: the tool does not pick one of the two definitions.

stxt validate docs/book.stxt

/home/ana/libros/.stxt/otro/copia.stxt:0: [DISCOVERY_DUPLICATE_NAMESPACE] Duplicate definition for namespace 'com.acme.book' at level /home/ana/libros/.stxt: already defined in /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt (error)
1 error(s), 0 warning(s)

Without a grammar, or with the grammar as a warning

Grammars are optional in the language, but validate requires being able to apply them. A document without a namespace is not validated and passes (STXT-SCHEMA-SPEC §5). One whose namespace no grammar of the chain defines produces SCHEMA_NOT_FOUND, also when the chain is empty: a document that cannot be validated does not count as validated. The result depends only on the document and its chain, not on which other grammars are installed. To check the syntax alone, use --no-schema. A document that is a definition (@stxt.schema or @stxt.template) is always checked against its meta-schema, even when there is no other grammar at all.

Two options change what counts as a failure. --warn-schema downgrades grammar errors to warnings, intended for the introduction of a grammar into a project with documents that predate it:

stxt validate --warn-schema docs/book.stxt

/home/ana/libros/docs/book.stxt:6: [INVALID_VALUE] Published: Invalid date (1 de octubre de 2025) (warning)
/home/ana/libros/docs/book.stxt:1: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.book:isbn' and min is 1 (warning)
0 error(s), 2 warning(s)

It exits with 0: there are warnings, but no errors. --no-schema checks the syntax only, resolving and applying no grammar: the same document passes with no output.

JSON output

With --format json the output is an array with one object per finding —file, line, code, message, severity—, and [] when everything passes. There is no summary or extra text, so it can be piped straight into jq or read from another program. The exit code is the same as in text mode.

stxt validate --format json docs/book.stxt

[{"file":"/home/ana/libros/docs/book.stxt","line":6,"code":"INVALID_VALUE","message":"Published: Invalid date (1 de octubre de 2025)","severity":"error"},{"file":"/home/ana/libros/docs/book.stxt","line":1,"code":"TOO_FEW_CHILDREN","message":"0 nodes of 'com.acme.book:isbn' and min is 1","severity":"error"}]
# How many findings of each code there are in the whole project
stxt validate --format json --recursive docs/ | jq -r '.[].code' | sort | uniq -c

stxt format

stxt format <file|dir|->... [--recursive] [--tabs | --spaces] [--write | --check] [--clean]
            [--max-nesting N] [--max-line-length N] [--max-input-size N]

Rewrites documents in their canonical form: indentation normalised to tabs (or to four spaces with --spaces), a single space after the colon, no trailing whitespace. It works line by line: it re-renders the lines that open a node and keeps everything the tree does not describe —comments, blank lines and the content of >> blocks, which is only re-indented, its inner blank lines included (the final blank lines of a block are not content and stay as plain, unindented blank lines)—, plus the original line ending (CRLF included) and the absence of a final newline. Of a comment it converts only the whole indentation units (tabs or groups of four spaces) to the chosen style, one for one, and leaves whatever follows them untouched. The namespace is written only where the source wrote it.

Three modes, mutually exclusive:

Mode What it does Exits with 1 if…
(default) Prints the result to standard output; does not write to disk Some document does not parse
--check Lists the files that would change (<file>: would be reformatted); writes nothing Some would change, or does not parse
--write, -w Rewrites each file in place, only when it changes, and reports it (Formatted <file>) Some document does not parse

Other options:

Option Effect
--tabs / --spaces Indent with tabs (default) or with four spaces
--recursive, -r As in validate: descends, sorts by name, skips the .stxt/ directories
--clean Switches engine: re-serialises the logical tree, so comments and blank lines are lost
--max-nesting N, --max-line-length N, --max-input-size N The parser limits, as in validate; they reach both engines

--check is equivalent to gofmt -l or prettier --check, and it is the mode suited to CI. --clean obtains the pure canonical document; since it loses information, it is not the default. A document with syntax errors is reported and not reformatted, in any mode. format has no grammar mode: reformatting a document is independent of whether it validates.

A document written with spaces, a comment, a blank line and some extra spaces:

# Book record
Book (com.acme.book):
    Title:   Arquitectura de software moderna

    Authors:
        Author: María Pérez
    ISBN: 978-84-123456-7-8
stxt format docs/book.stxt

# Book record
Book (com.acme.book):
	Title: Arquitectura de software moderna

	Authors:
		Author: María Pérez
	ISBN: 978-84-123456-7-8

The comment and the blank line are preserved; the indentation is now tabs and the extra spaces have been removed. The other two modes on the same file:

stxt format --check docs/book.stxt
/home/ana/libros/docs/book.stxt: would be reformatted
# exit code 1

stxt format --write docs/book.stxt
Formatted /home/ana/libros/docs/book.stxt

stxt format --check docs/book.stxt
# writes nothing any more: exit code 0

Combining --write with --check, or --tabs with --spaces, is a usage error (2): stxt format: --write and --check cannot be combined.

With - the document is read from standard input and the result goes to standard output, which turns format into a filter any editor can apply to the selection; --check - answers <stdin>: would be reformatted and 1 if it would change. --write with - is a usage error: there is no file to write back to.

stxt format --spaces - < docs/book.stxt

stxt format --write -
stxt format: --write cannot be used with - (the standard input); the result is printed to stdout

stxt describe

stxt describe <file|-> [--max-nesting N] [--max-line-length N] [--max-input-size N]

Parses a single document and writes its logical tree to standard output in the canonical JSON of STXT-TREE-SPEC: an array with the root nodes, and for each node name, canonicalName, the effective namespace, the form ("inline" with value and children, or "block" with lines). It includes no positions, comments or derived fields. It allows any program that reads JSON to process an STXT document without a parser of its own.

describe resolves no grammar and validates nothing: those results are not part of the logical tree. If the document has syntax errors it emits no partial tree: it reports the errors on standard error and exits with 1. With - it reads the document from standard input (stxt describe - < config.stxt), and calls it <stdin> in the errors. It takes the same parser limits as validate (--max-nesting, --max-line-length, --max-input-size; -1 disables): stxt describe log.stxt --max-input-size -1 emits the tree of a document larger than the default input size.

Book (com.acme.book):
	Title: Arquitectura de software moderna
	Authors:
		Author: María Pérez
	ISBN: 978-84-123456-7-8
	Chapter: Introducción
		Content >>
			Conceptos básicos y objetivos del libro.
stxt describe docs/book.stxt

[
  {
    "name": "Book",
    "canonicalName": "book",
    "namespace": "com.acme.book",
    "form": "inline",
    "value": "",
    "children": [
      {
        "name": "Title",
        "canonicalName": "title",
        "namespace": "com.acme.book",
        "form": "inline",
        "value": "Arquitectura de software moderna",
        "children": []
      },
      {
        "name": "Authors",
        "canonicalName": "authors",
        "namespace": "com.acme.book",
        "form": "inline",
        "value": "",
        "children": [
          {
            "name": "Author",
            "canonicalName": "author",
            "namespace": "com.acme.book",
            "form": "inline",
            "value": "María Pérez",
            "children": []
          }
        ]
      },
      {
        "name": "ISBN",
        "canonicalName": "isbn",
        "namespace": "com.acme.book",
        "form": "inline",
        "value": "978-84-123456-7-8",
        "children": []
      },
      {
        "name": "Chapter",
        "canonicalName": "chapter",
        "namespace": "com.acme.book",
        "form": "inline",
        "value": "Introducción",
        "children": [
          {
            "name": "Content",
            "canonicalName": "content",
            "namespace": "com.acme.book",
            "form": "block",
            "lines": [
              "Conceptos básicos y objetivos del libro."
            ]
          }
        ]
      }
    ]
  }
]
# The book's title, with jq
stxt describe docs/book.stxt | jq -r '.[0].children[] | select(.canonicalName == "title") | .value'

stxt schemas

stxt schemas [path]

Shows the resolution chain of a document or a directory —by default, the current directory— and, for each namespace, which definition is active and which file it comes from. It answers which grammar applies to a document, and is the first diagnostic for a SCHEMA_NOT_FOUND.

stxt schemas docs

Resolution chain for /home/ana/libros/docs:
    /home/ana/libros/.stxt

Namespaces:
    com.acme.book <- /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt

The chain lists the levels in order of precedence: the .stxt/ directories of the document and of its ancestors, then the user and system ones if they exist. If the project sits inside another with its own .stxt/ (a monorepo), both appear, and for each namespace the closest one wins. When there is no level at all:

stxt schemas

Resolution chain for /home/ana/notas:
    (empty — no .stxt directory found)

No namespaces resolved.

Errors of the chain are shown in an Errors: block at the end, with their code. The usual case is a copy of the same grammar in two paths of the same level: the namespace is left without an active definition until one is removed, and validate fails with the same error.

stxt schemas docs

Resolution chain for /home/ana/libros/docs:
    /home/ana/libros/.stxt

No namespaces resolved.

Errors:
    DISCOVERY_DUPLICATE_NAMESPACE /home/ana/libros/.stxt/otro/copia.stxt: Duplicate definition for namespace 'com.acme.book' at level /home/ana/libros/.stxt: already defined in /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt

schemas always exits with 0, even with errors in the chain: it is an informational command; validate is the one that fails.

stxt install

stxt install <file> [--local | --user | --system | --root <dir>] [--force] [--ignore-non-definitions]

Installs the definitions —schemas and templates— of a file into a level of the resolution chain. It is not a copy of the file: it first checks that it has the .stxt extension, that it parses and that each of its root nodes is a definition that validates against its meta-schema (the same check the resolver performs when loading a level), and only then it writes, all or nothing. Each definition is written separately, in canonical form, as <level>/@stxt.schema/<namespace>.stxt or <level>/@stxt.template/<namespace>.stxt, named after the namespace it defines, not after the source file; a file with several definitions is split into several.

That naming is a convention of the CLI, not of the language: STXT-DISCOVERY-SPEC §3 gives no meaning to file names or to the subdirectories of a level, so a grammar placed by hand may be at any path of the level. The convention makes visible, with an ls, which namespaces each level defines.

Level Option Where it writes
Project --local ./.stxt (the current directory; default)
User --user ~/.stxt (%USERPROFILE%\.stxt on Windows)
System --system /etc/stxt (%ProgramData%\stxt on Windows)
Any --root <dir> The given directory, whether it exists or not
Option Effect
--force Overwrites a definition already installed for that namespace (path match or namespace match)
--ignore-non-definitions Installs the definitions of the file and skips the other root nodes, instead of failing
stxt install book-template.stxt

Installed com.acme.book (@stxt.template) to /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt

The cases that make the installation fail (code 1) and their messages:

stxt install book-template.stxt
stxt install: /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt already exists (use --force to overwrite)

stxt install docs/book.stxt
stxt install: docs/book.stxt:1: root node 'Book' belongs to 'com.acme.book', not to @stxt.schema or @stxt.template (use --ignore-non-definitions to install only the definitions)

stxt install broken.stxt
stxt install: broken.stxt: invalid @stxt.template definition: Type not valid: NOTATYPE

stxt install notes.txt
stxt install: not an STXT document (.stxt expected): notes.txt

A file that mixes a template with example documents is installed with --ignore-non-definitions: the template is installed and the examples are skipped. To share a grammar among every project on a machine, stxt install --user grammar.stxt.

Grammar resolution and STXT_PATH

The three commands that apply grammars —validate, schemas and install— use the same resolution chain as the VS Code extension and the libraries, the one of STXT-DISCOVERY-SPEC §4: for a document, every .stxt/ directory from its folder upwards, then ~/.stxt and finally /etc/stxt. Inside each level every .stxt file is loaded, recursively, and each must be a definition. Precedence is per namespace: for each one the closest level that defines it prevails, and the other levels keep contributing the ones it does not define. Two definitions of the same namespace at the same level are an error, and that namespace is left without a definition.

The STXT_PATH environment variable replaces the whole chain with a list of directories separated by : (; on Windows), in order of precedence; the entries need not be called .stxt, and one that does not exist contributes nothing. It allows a CI job to be independent of whatever the machine running it has in ~/.stxt or /etc/stxt:

STXT_PATH=./.stxt stxt validate --recursive docs/

A STXT_PATH that is set but empty leaves the chain empty: no document with a namespace finds a grammar, so validate fails with SCHEMA_NOT_FOUND — a document that cannot be validated does not count as validated. Checking the syntax alone in that case is requested explicitly with --no-schema (or the error is downgraded to a warning with --warn-schema), and schemas shows the chain as (empty — STXT_PATH provides no directories). The CLI adds no rule of its own to this chain; the whole policy lives in the library, which is why it is identical in every tool.

Common tasks

Continuous integration. Two steps: the documents validate and are well formatted. Fails with 1 if something does not pass, and with 2 if the invocation is incorrect.

npx @stxt-lang/cli validate --recursive docs/
npx @stxt-lang/cli format --check --recursive docs/

A Makefile.

check:
	stxt validate --recursive docs/
	stxt format --check --recursive docs/

fmt:
	stxt format --write --recursive docs/

Formatting a whole project for the first time, switching it to spaces:

stxt format --write --spaces --recursive .

The .stxt/ directories are skipped; the grammars are formatted in a separate call. The findings as data —for instance, only the errors of one specific code—:

stxt validate --format json --recursive docs/ | jq '.[] | select(.code == "SCHEMA_NOT_FOUND")'

A document as JSON for another program:

stxt describe config.stxt > config.json

A document that is not on disk —another program generates it, or it comes from an HTTP request—, with -:

curl -s https://example.com/api/book.stxt | stxt validate -
curl -s https://example.com/api/book.stxt | stxt describe - | jq '.[0].name'

Limits

What the command line does not do:

  • It does not read from standard input by default: it is requested with -, and only validate, format and describe accept it; schemas and install work on paths.
  • It does not convert to other formats: the only structured output is the JSON of describe (the STXT-TREE-SPEC tree) and that of validate --format json.
  • There is no per-command help: stxt --help is all the help, and this page is the reference.
  • There is no parser inside. A parsing or validation error belongs to the @stxt-lang/core library —and is therefore shared by every tool—; the place to report it is stxt-js. Errors in options, messages or behaviour of the command go to stxt-cli.