CX · Reference guideQuickstart — first 5 minutes with CX
PlaygroundDownloadsAboutv0.18.0-pre.1

Quickstart — first 5 minutes with CX

One bracket form is your data, your query and your program, so the first file you write is also the first thing you query, validate, hash and serve. Python set the standard for a language that is productive on day one; CX is built to that standard and past it where data work hurts (cx-vs-python). The whole idea in one replayed fixture — a document, a query that is a pattern over it, and the live output:

input.cx

      [users
  [user active=true [name Alice] [email a@x.com]]
  [user active=false [name Bob] [email b@x.com]]
  [user active=true [name Carol] [email c@x.com]]]
    

prog.cx

      [?for [user @active=true [email $e]] [yield $e]]
    
      $ cx --data=input.cx prog.cx
'a@x.com'
'c@x.com'
    

Fixture program-for-002-active-emails in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The rest of this page 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 self-contained binary cx plus a shared library libcx for host-language bindings. Every release publishes the four builds of it for macOS on Apple Silicon and for Linux on arm64, and the installer picks the one for your machine:

      curl -sSL https://cxhome.org/install | sh
         cx --version
    

The installer resolves the latest release, verifies the tarball against the release's SHA256SUMS.txt and installs the platform build under ~/.local by default (PREFIX= overrides); leaner builds install with CX_PROFILE=data, CX_PROFILE=embed, or CX_PROFILE=cli. Every tarball is linked from the guide's downloads page. On a machine with no prebuilt build — Linux on x86_64, an Intel Mac, Windows — the installer says so and points at the source build.

Build from source — the V toolchain comes from the vendored fork at third_party/v, a git submodule, so clone with --recursive. The build fetches its pinned repositories with a released cx: make deps-sync finds one on PATH or at ~/.local/bin/cx, and CX_BIN= names another.

      git clone --recursive https://github.com/cx-home/cx
         cd cx
         make -C third_party/v   # the vendored V toolchain, once
         make deps-sync          # the pinned repositories, under deps/
         make build-vcx          # libcx + cx, at deps/cx-core-code/vcx/target/cx
         make install-cli        # /usr/local/bin/cx; PREFIX= overrides
         cx --version
    

Per-platform notes — loader paths, editor wiring, the WebAssembly bundle — are 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 double quotes are gone — canonical output drops quotes entirely when a token is bare-safe, and attribute order is preserved as written; attributes are never re-sorted, not even 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, one line per yielded item:

      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              [# lossless canonical — one line, double quotes, comments kept #]
         cx canonical users.cx        [# strict canonical bytes — indented, quotes dropped where bare-safe #]
    

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

      cx hash users.cx
         [# → sha2-256:3ace03ec...7a13c9e6  (tagged; 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. JSON, YAML, TOML, and XML round-trip losslessly under the --lossless flag; the default JSON projection is idiomatic and can drop CX-only detail such as mixed content; Markdown and CSV are lossy by design. 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 --from=json --to=cx users.json > users.cx
    

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

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

The document's identity is also stable through a lossless projection round-trip — cx hash users.cx equals cx --from=json --to=cx users.json | cx hash — because hashing is computed on canonical bytes (see canon-cross-format). The lossy projections (default JSON for mixed content, Markdown, CSV) are the named exceptions.

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 the CXPath sections of spec/03-approved/core/code.md and the conformance corpus conformance/xpath_31_parity.cxd 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 of=users mode=strict]
         [users [elem user [card "1..*"]]]
         [user
           [attr id::int [req]]
           [attr email::string [req]]
           [attr active::bool [req]]]
    

Run the validator:

      cx validate users.cx --schema=users.cxs
         [# → prints nothing; exit code 0                              #]
    

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

      cx validate users.cx --schema=users.cxs
         [# → users.cx:2:4: error: S002: missing required attribute 'active' on <user>  #]
    

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

         with open('users.cx', 'rb') as f:
             doc = cxlib.Doc.parse(f.read())
         print(doc.hash())                       [# tagged sha2-256:… #]
         for user in doc.select_all('//user[= $_@active true]'):
             print(user.attr('email'))
    

V (native — imports the V core directly):

      import os
         import native as cx

         doc := cx.parse_doc(os.read_file('users.cx')!)!
         println(doc.hash()!)
         for user in doc.select_all('//user[= $_@active true]')! {
             println(user.attr('email') or { continue })
         }
    

Go:

      import "github.com/cx-home/cx/lang/go/cxlib"

         src, _ := os.ReadFile("users.cx")
         doc, _ := cxlib.ParseDoc(src)
         h, _ := doc.Hash()
         fmt.Println(h)
         users, _ := doc.SelectAll("//user[= $_@active true]")
         for _, u := range users {
             fmt.Println(u.Attr("email"))
         }
    

Rust:

      use cxlib::code::Doc;

         let doc = Doc::parse(&std::fs::read("users.cx")?)?;
         println!("{}", doc.hash()?);
         for user in doc.select_all("//user[= $_@active true]")? {
             println!("{}", user.attr("email").unwrap());
         }
    

The identity-critical operations — hashing, canonicalization, program eval — are one implementation (the V core that libcx is compiled from), so their results are bit-identical across bindings. Parsing and several emitters are implemented natively per binding and held to parity by the conformance corpus: the Layer-1 parity gate in spec/03-approved/misc/bindings.md, tracked per binding in spec/03-approved/misc/parity-matrix.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 directive registry, CXPath in depth, patterns, builtins, services / workers / async, and resilience.
  • Browse the modules. CX ships 92 modules, each declared once in registry/modules.cxd; the nav lists every bundled one under the ring or group that owns it.
  • 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 states CX's case against JSON, YAML, TOML, XML, Protobuf, MessagePack, Pkl, Dhall, Parquet, and Arrow, starting beside Python — the standard CX is built to meet and exceed (cx-vs-python).
  • 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 and read the spec: approved documents live under spec/03-approved/, in-flight work under spec/02-working/. 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 installer and source paths at a glance; this section carries the full per-platform breakdown including loader path and editor wiring.

Release status

Every release publishes the four builds (platform, cli, embed, data) as tarballs for macOS on Apple Silicon and for Linux on arm64, plus a conformance bundle, the VS Code extension and SHA256SUMS.txt over all of them; the installer one-liner curl -sSL https://cxhome.org/install | sh picks the tarball for your machine. A Homebrew tap and a Docker image are not published. Production users should pin to a release tag.

macOS

        curl -sSL https://cxhome.org/install | sh
      

Installs the prebuilt Apple Silicon platform build; CX_PROFILE=data|embed|cli selects a leaner one. No Intel build is published; on an Intel Mac the installer says so and points at the source build (§0.10.6). A Homebrew tap is not published.

Linux

        curl -sSL https://cxhome.org/install | sh
      

On arm64 the installer installs the prebuilt Linux build, which the release pipeline compiles in ubuntu-22.04. No x86_64 build is published; there, build from source (§0.10.6).

Windows

No Windows build is published, and the installer refuses an operating system other than macOS or Linux (the V toolchain's Windows support is still on the roadmap — see faq).

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. It is not a release asset: make guide-wasm builds it from source with emscripten (which devbox.json pins) and renders the guide around it.

Build from source

        git clone --recursive https://github.com/cx-home/cx
           cd cx
           make -C third_party/v   # the vendored V toolchain, once
           make deps-sync          # the pinned repositories, under deps/
           make build-vcx          # libcx + cx, at deps/cx-core-code/vcx/target/cx
           make install-cli        # /usr/local/bin/cx; PREFIX= overrides
      

The build compiles with the vendored V fork at third_party/v, and make deps-sync fetches the repositories deps.cxd pins with a released cx — one on PATH, at ~/.local/bin/cx, or named by CX_BIN=. 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. The cx binary is self-contained — it does not link libcx. If a host-language binding cannot find libcx, point it at the library with LIBCX_PATH (full path to the library file) or LIBCX_LIB_DIR (its directory).

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.

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

Two comment forms, both valid at document level and inside element bodies. # starts a line comment; [; … ] is a block comment that can span lines.

        # top pick this week
         [shop port=8080
           [; the menu is trimmed for the tutorial ]
           [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; in body position it appears only inside the [ref @id] reference form. & 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. The cells that follow partition into rows by the declared column count — newlines are ordinary whitespace, so one row per line is a readability convention, not a rule. Cells parse against the declared column types, and the table round-trips to CSV with types preserved via the header row. In the binary ast-bin form, the table record stores rows row-major; the column-major layout is the separate CXCol format (see analytics).

        [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 any of six document dialects — CX, XML, JSON, YAML, TOML, or Markdown. Each dialect captures the subset of the data model it can represent. Beyond the six, the delimited surfaces (CSV / TSV / PSV) and the columnar binary surface (CXCol) convert through the same codec layer — see analytics.

        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']
         [?let [= $t //page/@title] [h1 [$string $t]]]
      

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

[?if] is strictly two-branch — a condition, a [then], an [else]. Multi-way dispatch on a value is [?match]: [case] arms carry a literal or pattern plus a result, tried in order, and [else] is the catch-all.

        [?let [= $size 'medium']
             [?match $size
               [case 'small' 9] [case 'medium' 12] [case 'large' 15]
               [else 0]]]
      

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

[?cx include=PATH] splices another file's children into the parent document at parse time. Resolution is opt-in: without an include root the directive is preserved untouched; with --include-root every include resolves during the parse. Paths are relative-only and bounded by the root — no absolute paths, no URLs, no escaping the root — and include depth defaults to 8. Errors are the data-parse codes cx-err:E901–E911.

        [users
             [?cx include=lib/admins.cx]
             [user name=carol]]
      
        cx --xml --include-root=. config.cx
      

Identity

Every CX document has a canonical form: presentation stripped, attribute order preserved as written, anchors resolved and erased (the merge is materialized). 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.