Quickstart — first 5 minutes with CX

This is the shortest path from "I have never seen CX" to "I have parsed, hashed, queried, and validated my first document, and called CX from my host language." Eight steps, five minutes if your toolchain is warm. If you only have time for one section, read this one — then jump to next-steps.

Install

CX ships as a single statically-linked binary `cx` plus a shared library `libcx` for host-language bindings. Today the supported install path is a build from source:

**Build from source.** The reference build uses devbox to pin the V toolchain and Python version. From a fresh clone of the repository:

          git clone https://github.com/cx-home/cx
         cd cx
         devbox run -- make build
         devbox run -- make install   [# installs to ~/.local/bin     #]
         cx --version
        

**Packaged installs** — a Homebrew tap, prebuilt release tarballs, and a Docker image — are planned but not published yet; distribution is being decided in the tracker. Until then, every platform builds from source (per-platform notes in §0.10).

If `cx --version` prints a version line, you are ready. If you see `command not found`, your `PATH` does not include the install location — see faq for recovery steps.

Hello, CX

Every interactive CX session starts the same way: pipe a CX literal into the `cx` binary and see what comes out.

          echo '[hello greeting="world"]' | cx
        

The output is the same shape you typed:

          [hello greeting='world']
        

Three things to notice. **First**, the value evaluated to itself — data and code share one syntax, and a bare data element is its own value (see homoiconicity). **Second**, the canonical form normalises double quotes to single quotes; attribute order is preserved at parse time but re-sorted lexicographically for hashing (see canonical). **Third**, no closing tag duplication — every CX element is a single bracketed form. That is the whole syntax.

Try a tiny program too. The `[?for]` directive iterates and yields:

          echo '[?for [in $x (1, 2, 3)] [yield [n $x]]]' | cx
        
          ([n 1], [n 2], [n 3])
        

Your first file

Real CX work lives in files. Create `users.cx` with three users:

          [users
           [user id=1 email='ada@example.com'    active=true]
           [user id=2 email='grace@example.com'  active=false]
           [user id=3 email='linus@example.com'  active=true]]
        

**Parse it** (round-trips through the canonicalizer; useful as a syntax check):

          cx fmt users.cx              [# pretty-print, lossless       #]
         cx canonical users.cx        [# strict canonical bytes        #]
        

**Hash it** — the SHA-256 of the strict canonical bytes is the document's identity (see hash):

          cx hash users.cx
         [# → 7f3a9b...c4e2  (64 hex chars)                            #]
        

The hash is stable across machines, OS versions, and bindings. Two documents with the same hash are byte-identical in canonical form.

Convert JSON ↔ CX

CX speaks JSON (and YAML, TOML, XML, Markdown) as projections — every projection round-trips. Start from JSON:

          cat > users.json <<'EOF'
         {"users":[
           {"id":1,"email":"ada@example.com","active":true},
           {"id":2,"email":"grace@example.com","active":false}
         ]}
         EOF
         cx convert --to cx users.json > users.cx
        

`users.cx` now contains the CX projection of that JSON. Convert back and verify the round-trip:

          cx convert --to json users.cx > users.roundtrip.json
         diff <(jq -S . users.json) <(jq -S . users.roundtrip.json)
         [# → no output: byte-identical after canonicalization        #]
        

The hash is also stable through the round-trip — the CX form of a JSON document and the round-tripped JSON form share the same content hash, because hashing is computed on canonical bytes (see canon-cross-format).

Select with CXPath

CXPath is the universal selector — for reading, for iteration, for update focus. Same vocabulary everywhere. The simplest query asks: "which users are active?"

A CX program is just another document, so the quickest CLI query appends one to your data and runs the result — the program's `//` paths resolve against the document that precedes it:

          echo '[?for [in $u //user[= $_@active true]] [yield $u@email]]' \
           | cat users.cx - | cx
        

Read the predicate as: "from anywhere in the document (`//`), find a `user` element whose `@active` attribute equals `true`, then take its `@email` attribute." Predicates are ordinary prefix CX code over the context node `$_` — `[= $_@active true]`. If you have written XPath, the alignment is deliberate — see [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md) for what carries over and what does not.

The same path drives iteration in a program:

          [users
           [user email='ada@example.com' active=true]
           [user email='bob@example.com' active=false]]
         [?for [in $u //user[= $_@active true]] [yield
           [active-user email=$u@email]]]
        

Validate with a schema

A CX schema (`.cxs`) is a CX document that describes the allowed shape of another CX document. Write `users.cxs`:

          [schema target='users'
         [element name='users' mode=strict
           [child name='user' min=1 max='*'
             [attr name='id'     type=int  required=true]
             [attr name='email'  type=str  required=true]
             [attr name='active' type=bool required=true]]]]
        

Run the validator:

          cx validate --schema users.cxs users.cx
         [# → users.cx: ok (3 user elements validated)                 #]
        

Break it on purpose to see a diagnostic. Remove `active` from one user and re-run:

          cx validate --schema users.cxs users.cx
         [# → users.cx:2:3 CXER0301: missing required attribute 'active'  #]
        

Schema modes are `open` / `strict` / `closed` — strict rejects unknown attributes, closed also rejects unknown child elements. See schema for the full constraint vocabulary.

Hello from your host language

CX ships four Tier-1 host-language bindings — V (the native reference), Python, Go, and Rust. The Layer-1 method set is identical across all four (see layer-1). One install, one shape:

**Python:**

          pip install cxlib
        
          import cxlib

         doc = cxlib.parse_file('users.cx')
         print(doc.hash())                       [# stable SHA-256    #]
         actives = doc.eval('//user[= $_@active true]/@email')
         for email in actives:
             print(email)
        

**V** (native — `cxlib` is `import cx`):

          import cx
         doc := cx.parse_file('users.cx') or { panic(err) }
         println(doc.hash())
         println(doc.eval('//user[= $_@active true]/@email'))
        

**Go:**

          import "github.com/cx-home/cx-go"
         doc, _ := cx.ParseFile("users.cx")
         fmt.Println(doc.Hash())
         res, _ := doc.Eval("//user[= $_@active true]/@email")
         fmt.Println(res)
        

**Rust:**

          use cxlib::Doc;
         let doc = Doc::parse_file("users.cx")?;
         println!("{}", doc.hash());
         let res = doc.eval("//user[= $_@active true]/@email")?;
         println!("{}", res);
        

All four bindings call into the same compiled `libcx`; hash values, eval results, and canonical bytes are bit-identical across them. That is enforced by gate 28.6 in [`spec/governance.md`](../../spec/governance.md).

Where to go next

You now have CX installed, you can write data, hash it, convert it, query it, validate it, and call it from your host language. From here, follow your interest:

  • **Learn the data language end-to-end.** intro walks the design philosophy; data is the full reference for scalars, collections, sigils, and mixed content.
  • **Learn the code language.** code covers the 40 directives, CXPath in depth, patterns, builtins, services / workers / async, and resilience.
  • **Wire a binding into a real project.** bindings has per-language quickstarts and the Layer-1 / Layer-2 method set.
  • **Pick a tooling integration.** tooling covers the LSP, tree-sitter grammar, VS Code, Neovim, Helix, `cx lint`, `cx diff`, and the playground.
  • **Migrate an existing codebase.** migration covers prior-version migration and intake from JSON / YAML / TOML / XML.
  • **Compare against alternatives.** comparison is the side-by-side against JSON, YAML, TOML, XML, Protobuf, MessagePack, Pkl, Dhall, Parquet, and Arrow — including where CX is the wrong choice.
  • **Look up a term.** glossary defines every CX-specific term used in this guide; faq covers the first-week-of-CX questions.

The community lives at https://github.com/cx-home/cx — file bugs, browse RFCs, and read the spec under `spec/*.md`. The guide ships an in-browser playground (`make guide` renders it to `docs/guide/playground.html`) that runs CX with no install.

Install — per-platform detail

Per-platform install instructions. Section §0.1 covers the brew / docker / source paths at a glance; this section carries the full per-platform breakdown including loader path and editor wiring.

Release status

Every platform currently builds from source with `devbox run -- make build`; prebuilt binaries, a Homebrew tap, and a Docker image are planned but not published yet. Production users should pin to a release tag.

macOS

              git clone https://github.com/cx-home/cx && cd cx
           devbox run -- make build && devbox run -- make install
            

Builds native on Apple Silicon and Intel. A Homebrew tap is planned but not published yet.

Linux

Build from source with `devbox run -- make build`; the result is a static binary that runs on any glibc-based distro. A prebuilt Linux tarball on the GitHub Releases page is planned but not published yet.

              git clone https://github.com/cx-home/cx && cd cx
           devbox run -- make build && devbox run -- make install
            

Windows

A static `cx.exe` is planned per release on github.com/cx-home/cx/releases, but is not published yet (the V toolchain's Windows support is still on the roadmap — see faq); build from source in the meantime. Drop the binary on PATH; no installer, no dependencies.

WebAssembly bundle

The `libcx-wasm` bundle powers the live evaluator in the playground; it exports the `_cx_code_eval` family plus `_cx_code_diagram` and `_cx_code_tree`. The bundle ships alongside the `cx` binary on release tags.

Build from source

              git clone https://github.com/cx-home/cx
           cd cx
           devbox run -- make build-vcx
           devbox run -- make test
            

The build chain expects V on PATH or the `cx-home` V fork at `third_party/v`; the Makefile auto-detects either. Check out the latest release tag to build a specific release.

Verify the install

              cx --version
           cx --json - <<< "[hello world]" 
            

A successful run prints the version and a JSON document on stdout. If either fails, the build needs `libcx` on the loader path (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS).

Twenty-chapter walkthrough

This walkthrough walks CX from the smallest piece to the fullest expression in twenty linked chapters. Each chapter introduces one shape, shows it in context, and ends with two or three exercises you can run with `cx --ast` or `cx --json`. Pace yourself. Assumed background: familiarity with at least one of JSON, YAML, TOML, or XML — CX assumes you know what an object, array, scalar, and attribute are. Designer pre-approved as front-matter (decision recorded in `_migration_plan.md`).

Why CX looks the way it does

Most projects need two kinds of file: typed configuration in nested structures, and documents with prose plus structure. JSON, YAML, and TOML are optimized for the first; Markdown, HTML, and XML for the second. Projects end up with multiple file formats and multiple parsers. CX is one syntax for both. Five design constraints picked the shape: one construct, no indentation semantics, optional quoting, types first-class, lossless conversion to existing formats.

Elements

Every CX node is a bracket pair with a name and an optional body. Editors balance brackets natively; code folding works without parser hooks. The opening name appears once; nothing repeats at close.

              [p Hello, World]
            

**Exercises.** Save as `hello.cx` and run `cx --ast hello.cx`. Add a child element and re-run. Then run `cx --json` on the same file.

Attributes

Attributes are name=value pairs after the element name. Bare values auto-type: `8080` becomes int, `3.14` becomes float, `true` becomes bool, `2026-01-01` becomes date. Leading-zero tokens stay strings — `02134` stays the string `02134` — to avoid the YAML Norway problem.

              [server host=api.example.com port=8080 ssl=true]
            

**Exercises.** Add three more attributes and check what type cx assigns each. Quote a value containing a space and inspect the AST.

Types

CX auto-types most values; for the rest, a glued `::T` ascription pins the value or body type. Sized variants carry range constraints that validate at parse time.

              [port::u16 8080]
            

**Exercises.** Try setting the `::u16` value to `70000` and observe the error. Compare `[n 12]` vs `[n::decimal 12]` in `cx --ast`.

Element bodies

A body holds Text, Scalar, child Element, or collection literal in source order. Mixed content is first-class — text and structure interleave the way they do in XML.

              [para Try the [b Margherita] — our [em best seller], hand-tossed daily.]
            

Triple-quoted bodies preserve multi-line content verbatim. **Exercises.** Write an article with `h1`, `p`, `em`, `a` children; convert with `cx --md`.

Comments

Hash line comments live outside element bodies. Bracket-semicolon block comments live inside elements and can span lines.

              [shop port=8080 [menu [pizza name=Margherita]]]
            

**Exercises.** Check what `cx fmt` preserves and what `cx canonical` drops.

Sigils

Five sigils give CX structural reach beyond name and attribute. `@` is an attribute prefix in path expressions and a reference in body position. `&` declares an anchor; `*` merges from an anchor; `#` declares an id; `:` pins a data type.

              [defaults &shared timeout=30 retries=3]
           [server *shared name=api]
            

Collection literals

Three first-class collection shapes. Sequence with parens flattens; array with square brackets preserves nesting; map with braces gives named key-to-value entries. Collections live in an element body — attributes hold a single scalar, never a collection.

              [pizza [toppings (cheese, basil, oil)]]
           [pizza [prices {small: 9, medium: 12, large: 15}]]
            

Typed tables

A `[table[…]]` block declares column names and types. Each non-blank line below is a row; cells parse against the declared column types. Round-trips to CSV with types preserved via the header row. In the binary ast-bin form, rows are stored column-major and run 5 to 10 times smaller than equivalent element-per-row encoding.

              [orders [table[item::string qty::u32 paid::bool when::date]]
             Margherita 2 true  2026-05-09
             Hawaiian   1 false 2026-05-09]
            

Multi-document files

A single CX file can hold multiple top-level documents separated by three dashes on a line by themselves. Same separator as YAML; each side parses as an independent document, and `cx --json` emits the file as an array of documents.

Six surface dialects

The same tree parses and emits as CX, XML, JSON, YAML, TOML, or Markdown. Each dialect captures the subset of the data model it can represent.

              cx --yaml pizza.cx
            

CX code — directives inside data

CX code is CX that does things. A directive is an element whose name starts with `?`. Directives parse as regular CX and evaluate against the surrounding tree when `cx` runs the document.

              [page title='Menu — New Haven']
         [h1 //page/@title]
            

Path expressions (CXPath)

CXPath is the query language inside CX code. Paths separated by `/`; predicates in `[…]` are prefix CX code over the context node `$_`; axes navigate by relationship.

              [menu [pizza name=Margherita price=12]
               [pizza name=Marinara price=9]]
         //pizza[> $_@price 10]
            

Let and for

`[?let]` binds a name once and reuses it. `[?for]` iterates a path expression result.

              [?let [= $tax 0.22] [= $price 12] [* $price [+ 1 $tax]]]
            

Filters

Filter functions transform values. Compose them with `[?pipe]` — bare stages, threaded left to right — or nest them.

              [?lib 'cx-stdlib/strings']
[?pipe '  margherita  ' [$strings:trim] [$strings:upper]]
            

Multi-branch conditional

The multi-branch `[?if]` shape avoids the else-if ladder. Each branch is a condition-value pair; the wildcard branch is the catch-all.

Named templates

`[?def]` defines a reusable fragment; call it like any other function — `[$line …]` — with positional args.

              [?def line ($item) [tr [td $item/@name] [td $item/@qty]]]
            

Includes

Two include forms share one resolver. The parse-time form expands files before the document reaches downstream tools; the eval-time form expands files while a CX program runs.

              cx --xml --include-root=./conf config.cx
            

Identity

Every CX document has a canonical form: presentation stripped, attribute order normalised, anchors renumbered. `cx hash` returns the SHA-256 of those canonical bytes. Two documents with the same data have the same hash, in any dialect.

              cx hash menu.cx
            

Putting it all together

Every shape composes naturally with every other. The whole-shop example at §14.50 and the whole-kitchen CX code example at §15.50 exercise every shape in this tutorial inside a single working document.