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). --versionand--helpare honoured in any position and take precedence over the rest:stxt validate --helpprints 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
2without 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
formatanddescribe(the report ofvalidateis the result, and goes to standard output). -denotes standard input invalidate,formatanddescribe, 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:
formatonly writes with--write, andinstallonly 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 onlyvalidate,formatanddescribeaccept it;schemasandinstallwork 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 ofvalidate --format json. - There is no per-command help:
stxt --helpis all the help, and this page is the reference. - There is no parser inside. A parsing or validation error belongs to the
@stxt-lang/corelibrary —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.