Docs
View as markdown

Output modes

The nine relate CLI output modes with the exact bytes each prints for one query, the default for a terminal, a pipe and agent mode, --pretty and --noheader.

cli

An output mode decides how a query run prints each result set. The relate CLI has nine: table, csv, json, jsonl, envelope, markdown, cypher, toon and speedscope. Without an output flag, the mode is table when stdout is a terminal and jsonl when stdout is a pipe or a file, or toon when stdout is piped and agent mode is on. An explicit output flag always wins. The mode changes how results print, never what a statement One unit of Cypher that relate parses, plans and runs, such as a MATCH … RETURN read or a CREATE write; a script holds several, separated by ;. Read more in the glossary does. Its exit code is the same in every mode except speedscope, which exits 1 after a statement that has no PROFILE metrics; that statement still ran, and its writes committed.

Synopsis

relate [OPTIONS] --mode <mode> [--database PATH] [QUERY]
relate query --mode <mode> [STATEMENT] [--database PATH]
FlagEffect
--mode MODESelects one of the nine modes; an unknown name exits 2
--format MODEAlias of --mode
--json, --csv, --jsonl, --markdown, --toonShortcuts for --mode json, csv, jsonl, markdown and toon; the single-dash forms -json, -csv, -jsonl and -markdown are accepted
--prettyIndents json output even when stdout is not a terminal; the other modes ignore it
--noheaderDrops the header row of table and csv; the other modes keep their headers
--width NWraps table output to N display columns, 20 or more

Of several output flags, the last one given wins: relate --mode csv --json … prints JSON. The flag's value is the next argument; --mode=csv is an unknown flag. An unknown mode name exits 2:

relate --mode yaml "RETURN 1 AS ok"
echo "exit=$?"
Error: invalid mode 'yaml' (expected one of: table, csv, json, jsonl, envelope, markdown, cypher, toon, speedscope)
exit=2

Defaults

The CLI picks the mode once per run, from where stdout goes and whether agent mode is on:

stdoutAgent mode offAgent mode on
A terminaltabletable
A pipe or a filejsonltoon
Any, with an output flagthe flag's modethe flag's mode

Agent mode is on under a detected agent harness with stdout not a terminal, or with --agent-mode or RELATE_AGENT_MODE=1; the agent mode page has the rules. The interactive shell runs on a terminal, so its default is table. A script piped to relate query follows the same table: its output is jsonl unless something else applies.

The nine modes

ModeShortcutPrintsError object on stdoutRow cap reported in-band
tablenoneA boxed text table; strings in double quotesnono
csv--csvCSV with RFC 4180 quoting and LF line endings, with a header rownono
json--jsonOne JSON object per result set: the rows in data, with planCache and timings, and rowCap when the row cap cut them; indented on a terminal, one line when pipedyesyes
jsonl--jsonlOne JSON object per row, then one {"planCache":…,"timings":…} line per result setyesyes
envelopenoneOne JSON object per result set: columns, types, rows, flags, planCache and timingsyesyes
markdown--markdownA GitHub-flavored tablenoyes
cyphernoneOne CREATE statement per row, values as Cypher literalsnono
toon--toonOne rows[N]{cols}: header line, one line per row, then timings: and planCache: entriesnoyes
speedscopenoneA PROFILE plan as one speedscope profile documentyes, for a failed statement; the PROFILE refusal is stderr onlyno

A mode with no error object prints a failure only on stderr, with the exit code; the error object section covers the rest. json, jsonl, envelope and toon also report each statement's timings and plan-cache outcome; table, csv, markdown, cypher and speedscope do not. The in-band row cap report for each mode is on the agent mode page. An EXPLAIN or PROFILE result prints a plan object in json, jsonl, envelope and toon, in place of the shapes this table lists; EXPLAIN and PROFILE shows each.

One query in every mode

The examples on this page read the team graph shared across the docs: three people, two projects, and who works on what. This step writes it to team.rl:

cat > seed.cypher <<'EOF'
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace);
EOF
relate query --database ./team.rl < seed.cypher

Each example runs the same read, opened --read-only: who works on relate, and since when.

table

relate --database ./team.rl --read-only --mode table "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
+---------+-------+
| name    | since |
+---------+-------+
| "Grace" | 2023  |
| "Ada"   | 2024  |
+---------+-------+

csv

relate --database ./team.rl --read-only --csv "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
name,since
Grace,2023
Ada,2024

json

relate --database ./team.rl --read-only --json "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
{"data":[{"name":"Grace","since":2023},{"name":"Ada","since":2024}],"planCache":"miss","timings":{"elapsed_ms":0.498208,"execute_ms":0.288125,"lex_parse_ms":0.046125000000000006,"lower_ms":0.043583,"parse_ms":0.137083,"plan_ms":0.062541,"result_generation_ms":0.00033299999999999996,"rewrite_ms":0.008709000000000001}}

The rows are in data, beside the statement's planCache and timings. Keys are sorted alphabetically, in each row object and in the outer object, in json, jsonl and envelope alike. The column order is the statement's; envelope carries it in columns.

jsonl

relate --database ./team.rl --read-only --jsonl "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
{"name":"Grace","since":2023}
{"name":"Ada","since":2024}
{"planCache":"miss","timings":{"elapsed_ms":0.45545800000000003,"execute_ms":0.257291,"lex_parse_ms":0.033833,"lower_ms":0.044042000000000005,"parse_ms":0.12995800000000002,"plan_ms":0.059625,"result_generation_ms":0.00025,"rewrite_ms":0.013625000000000002}}

envelope

relate --database ./team.rl --read-only --mode envelope "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
{"columnTypes":["STRING","INTEGER"],"columns":["name","since"],"notifications":[],"planCache":"miss","rows":[{"name":"Grace","since":2023},{"name":"Ada","since":2024}],"timings":{"elapsed_ms":0.427292,"execute_ms":0.250583,"lex_parse_ms":0.028833,"lower_ms":0.040375,"parse_ms":0.111167,"plan_ms":0.057292,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.007708},"truncated":false}

markdown

relate --database ./team.rl --read-only --markdown "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
| name    | since |
|---------|-------|
| "Grace" | 2023  |
| "Ada"   | 2024  |

cypher

relate --database ./team.rl --read-only --mode cypher "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
CREATE ({name: 'Grace', since: 2023})
CREATE ({name: 'Ada', since: 2024})

cypher formats values, not graph elements: a node, relationship or path column has no Cypher literal form and is left out, with a // column <name> (<TYPE>) omitted: no Cypher literal form comment line. It is not a way to copy a store; back up and restore covers that.

toon

relate --database ./team.rl --read-only --toon "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
rows[2]{name,since}:
  Grace,2023
  Ada,2024
timings:
  {"elapsed_ms":0.43375,"execute_ms":0.261625,"lex_parse_ms":0.028458,"lower_ms":0.0405,"parse_ms":0.109291,"plan_ms":0.055292,"result_generation_ms":0.000334,"rewrite_ms":0.007333999999999999}
planCache:
  miss

The TOON Token-Oriented Object Notation: an output mode that prints the columns once, then one comma-separated line per row; the piped default in agent mode. Read more in the glossary header names the row count and the columns once; each row is two spaces, then the values separated by commas. The timings: and planCache: entries close the statement's block.

speedscope

speedscope needs a PROFILE statement, because the per-operator times of the plan are what it exports. The output is one JSON document in the speedscope file format: a frame per plan operator, named <operator> <id>, and an open and a close event per operator, in nanoseconds. The times change from run to run, so this example prints only the names:

relate --database ./team.rl --read-only --mode speedscope "PROFILE MATCH (p:Person) RETURN p.name AS name" | grep -o '"name":"[^"]*"'
"name":"relate PROFILE"
"name":"ProduceResults 0"
"name":"NodeByLabelScan 1"

A statement without PROFILE still runs, and its writes commit, but speedscope prints nothing on stdout and exits 1, with this message on stderr and no error object:

relate --database ./team.rl --read-only --mode speedscope "MATCH (p:Person) RETURN p.name AS name"
echo "exit=$?"
speedscope mode requires a PROFILE statement: per-operator times are what it exports (the statement carries no plan tree)
exit=1

A write is the case that matters: the refusal comes after the statement committed, so running it again writes twice. This example writes one Run node to a new directory store, ./sp, then counts it read-only:

relate --database ./sp --mode speedscope "CREATE (:Run)"
echo "exit=$?"
relate --database ./sp --read-only --mode jsonl "MATCH (r:Run) RETURN count(r) AS runs"
speedscope mode requires a PROFILE statement: per-operator times are what it exports (the statement carries no plan tree)
exit=1
{"runs":1}
{"planCache":"miss","timings":{"elapsed_ms":0.46045800000000003,"execute_ms":0.294958,"lex_parse_ms":0.031708,"lower_ms":0.0415,"parse_ms":0.105166,"plan_ms":0.052875,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.007458}}

How values print

The same row in each mode, with a string holding a comma and a pipe, a null, a list, a map, a float, a boolean and a date:

ModePrints
table"a,b|c", <null>, [1, 2], {k: 1}, 1.5, true, 2026-11-07
csv"a,b|c",,"[1, 2]",{k: 1},1.5,true,2026-11-07: null is an empty field
json, jsonl, envelope"a,b|c", null, [1,2], {"k":1}, 1.5, true, "2026-11-07": temporal values are strings
markdownAs table, with each pipe character escaped by a backslash
cypher{s: 'a,b|c', n: null, l: [1, 2], m: {k: 1}, f: 1.5, b: true, d: date('2026-11-07')}
toon"a,b|c",null,[1,2],{"k":1},1.5,true,2026-11-07: nested values are inline JSON, and a string is quoted, as a JSON string, when it is empty, holds a comma, a double quote, a backslash, or a control or invisible format character, has leading or trailing whitespace, or reads as a number (NaN and Infinity included), true, false or null

A node prints as (:Person {name: "Ada", role: "engineer"}) in table, csv and markdown, and as an object in the JSON modes and toon:

relate --database ./team.rl --read-only --mode jsonl "MATCH (p:Person {name: 'Ada'}) RETURN p"
{"p":{"id":0,"kind":"node","labels":["Person"],"properties":{"name":"Ada","role":"engineer"}}}
{"planCache":"miss","timings":{"elapsed_ms":0.540834,"execute_ms":0.3735,"lex_parse_ms":0.029833000000000002,"lower_ms":0.041832999999999995,"parse_ms":0.108875,"plan_ms":0.05025,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.00775}}

id is the node's internal id within this store. A relationship prints with "kind":"relationship".

Output safety

Text modes escape what could change a terminal or a spreadsheet. There is no flag to turn this off.

ModeWhat is escaped
table, markdownControl characters and invisible format characters, such as a bidi override, in every cell and header: a newline prints as \n, a tab as \t, others as \u{<hex>} in uppercase hex with no padding, such as \u{7} and \u{202E}; markdown also escapes |
csvControl characters other than a newline or carriage return, then a field starting with =, +, - or @ gets a ' prefix, so -5 prints as '-5; a field with a comma, quote or newline is quoted
cypherControl and invisible format characters inside string literals, as \n, \t and \uXXXX, such as \u0007
json, jsonl, envelope, toonJSON escaping only; values are unchanged
relate --csv "RETURN -5 AS n, '=1+1' AS formula"
n,formula
'-5,'=1+1

A program that reads csv output handles the ' prefix on a negative number; jsonl or envelope keeps numbers as numbers.

Empty results, writes, and several statements

Each statement prints its own result set, in the order the statements run:

ModeTwo statements, RETURN 1 AS a; RETURN 2 AS bA read with no rowsA write with no RETURN
tableTwo tablesThe header onlyNothing
csva, 1, b, 2 on four linesThe header lineOne empty line
jsonTwo objects, one per lineAn object with "data":[]An object with "data":[]
jsonlEach statement's rows, each followed by its timing lineThe timing line onlyThe timing line only
envelopeTwo envelopes, one per lineAn envelope with columns and "rows":[]An envelope with "columns":[]
markdownTwo tablesThe header and separator linesNothing
cypherOne CREATE per rowNothingNothing
toonTwo rows[1]{…}: blocks, each with its timings: and planCache: entriesrows[0]{name}:, then the timings: and planCache: entriesThe timings: and planCache: entries only

jsonl prints only the timing line for a read with no rows, the same as for a write. envelope tells the two apart:

relate --database ./team.rl --read-only --mode envelope "MATCH (p:Person {name: 'Zed'}) RETURN p.name AS name"
{"columnTypes":["ANY"],"columns":["name"],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":0.508375,"execute_ms":0.333625,"lex_parse_ms":0.028249999999999997,"lower_ms":0.039916999999999994,"parse_ms":0.110375,"plan_ms":0.054958,"result_generation_ms":0.000125,"rewrite_ms":0.007875},"truncated":false}

The envelope

--mode envelope prints one compact envelope One compact JSON object per result set, printed by --mode envelope, that keeps the column list even when there are no rows. Read more in the glossary per result set, on one line, with its keys sorted alphabetically:

KeyPresentHolds
columnTypesalwaysOne type name per column, from the first row; ANY for a null first value or no rows
columnsalwaysThe column names in the statement's order, even with no rows
notificationsalwaysThe statement's notifications; [] on a plain run
planEXPLAIN and PROFILE onlyThe plan tree
planCachealwaysThe plan-cache outcome, one of the five values
rowCapwhen the row cap cut the rows{"kept":N,"total":M}
rowsalwaysOne object per row, as json prints it
timingsalwaysThe statement's phase timings in milliseconds, one object with eight keys
truncatedalwaystrue when the row cap cut the rows

The agent mode page lists every columnTypes name. New keys may be added; existing ones are never renamed.

Timings and the plan cache

json, jsonl, envelope and toon report two facts for every statement that runs: where its time went, as a timings object, and whether a cached plan served it, as planCache. table, csv, markdown, cypher and speedscope print neither, and neither does the error object of a failed statement.

ModeWhere they print
jsonThe planCache and timings keys of the statement's object, beside data, and beside notifications and plan for EXPLAIN and PROFILE
jsonlOne {"planCache":…,"timings":{…}} line after the statement's other lines: its rows, then a {"rowCap":…} line when the row cap cut them, or its plan object. A statement with no rows prints only this line
envelopeThe planCache and timings keys of the envelope
toonA timings: entry, the object as inline JSON, then a planCache: entry, as the last two entries of the statement's block, after rowCap: and notifications:

timings holds eight keys, sorted alphabetically like every other object these modes print. Each value is a number of milliseconds, measured on this run, so the digits change from run to run:

KeyMeasures
elapsed_msThe statement's wall-clock total
parse_msThe parse phase
lex_parse_msLexing and building the syntax tree, a part of parse_ms
rewrite_msThe rewrite pass, a part of parse_ms
lower_msValidation and lowering, a part of parse_ms
plan_msThe plan phase
execute_msThe execute phase
result_generation_msBuilding the result, a part of execute_ms

On a plan-cache hit, lex_parse_ms, rewrite_ms and lower_ms are 0.0: the statement was not parsed again. planCache is one of five values:

ValueMeaning
missNo cached plan served the statement, so it compiled from the start
hit-raw-fastpathA cached plan served it because the statement text matched an earlier statement exactly
hit-templateA cached plan served it because the statement matched an earlier one with only its literal values changed
replan-generation-mismatchA cached plan matched, but the store had committed a write since it was compiled, so the statement planned again
bypassedThe cache was not consulted, such as for a parameter whose name starts with the reserved __AUTO_ prefix

The plan cache lives in the open store and starts empty on every CLI run, so a one-shot relate "…" reports miss. Several statements in one run share it. In this run, the second statement differs from the first only in a literal, and the third repeats the second:

relate --jsonl "RETURN 1 AS a; RETURN 2 AS a; RETURN 2 AS a" | jq -r 'select(.planCache) | .planCache'
miss
hit-template
hit-raw-fastpath

Errors

A failed statement prints its message on stderr and exits with the exit code of its class, in every mode. In json, jsonl, envelope and speedscope, it also prints an error object The JSON object a failed query run prints on stdout in the json, jsonl, envelope and speedscope modes: the class, the kind, the message, the exit code. Read more in the glossary on stdout, after any rows already printed. table, csv, markdown, cypher and toon print no error object:

relate --mode jsonl "RETRN 1"
echo "exit=$?"
syntax error: parse error: unexpected token "RETRN"
  RETRN 1
  ^
{"error":{"code":"validation_error","column":1,"exit_code":6,"kind":"syntax","message":"syntax error: parse error: unexpected token \"RETRN\"   RETRN 1   ^","retryable":false,"source":"RETRN 1"}}
exit=6

The first three lines are stderr; the error object is stdout. Its fields and every exit code are on the exit codes and errors page.

Header and width options

--noheader drops the header row of table and csv:

relate --database ./team.rl --read-only --csv --noheader "MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: 'relate'}) RETURN p.name AS name, w.since AS since ORDER BY since"
Grace,2023
Ada,2024

markdown and toon keep their header with --noheader, because the table or the row count needs it.

--pretty indents json when stdout is not a terminal, two spaces per level:

relate --json --pretty "RETURN 'Ada' AS name, 2024 AS since"
{
  "data": [
    {
      "name": "Ada",
      "since": 2024
    }
  ],
  "planCache": "miss",
  "timings": {
    "elapsed_ms": 0.48633299999999996,
    "execute_ms": 0.35312499999999997,
    "lex_parse_ms": 0.019084,
    "lower_ms": 0.0265,
    "parse_ms": 0.08887500000000001,
    "plan_ms": 0.036167,
    "result_generation_ms": 0.000166,
    "rewrite_ms": 0.006208
  }
}

--width N wraps table output to N display columns: the widest columns shrink and their cells continue on the next line. Without it, the width is the terminal's, else an exported COLUMNS, else the table's natural width. A value under 20 exits 2 with Error: invalid --width: a width under 20 columns cannot hold a table; omit --width for the terminal default.

See also