A CLI tool to render CSV/JSON/JSONL/YAML as pretty tables. It supports flattening of nested objects, selecting/excluding columns, filtering rows, and multiple output styles.
# From a JSON string
tablo -i '{"a":1,"b":2}'
# From a file
tablo -f demo/data/list.json
# From standard input
echo '{"a":1,"b":2}' | tabloCommand:
tablo -i '{"a":{"b":1},"tags":["x","y",3]}' --dive --flatten-simple-arraysOutput:
┏━━━━━━┳━━━━━━━━━┓
┃ KEY ┃ VALUE ┃
┣━━━━━━╋━━━━━━━━━┫
┃ a.b ┃ 1 ┃
┃ tags ┃ x, y, 3 ┃
┗━━━━━━┻━━━━━━━━━┛
Notes:
--diveflattens nested objects (e.g.,a.b).--flatten-simple-arraysconverts arrays of primitives into a comma-separated string.
Command:
tablo -F yaml --index-column --select 'name,age' --style ascii <<'YAML'
- name: Alice
age: 30
- name: Bob
age: 31
YAMLOutput:
+---+-------+-----+
| | name | age |
+---+-------+-----+
| 1 | Alice | 30 |
| 2 | Bob | 31 |
+---+-------+-----+
Notes:
--selectaccepts a comma-separated list of dotted paths. Use--select-fileto load column selections from a file (one per line).--index-columnadds an auto index column for row arrays.- Use
--limit Nto restrict the number of printed rows.
Tablo can parse CSV files or piped CSV data. The first row is treated as headers, and subsequent rows as data objects.
Command:
tablo -f demo/data/users.csv --select name,score --where 'score>90'Output:
┏━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ name ┃ score ┃
┣━━━━━━━━━━━━━━━╋━━━━━━━┫
┃ Alice Johnson ┃ 95.5 ┃
┃ Carol Davis ┃ 92.8 ┃
┃ Grace Lee ┃ 96.3 ┃
┗━━━━━━━━━━━━━━━┻━━━━━━━┛
JSONL format allows one JSON value per line. Arrays in JSONL are automatically flattened into individual rows.
Command:
tablo -f demo/data/users.jsonl --select name,departmentOutput:
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ name ┃ department ┃
┣━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━┫
┃ Alice Johnson ┃ Engineering ┃
┃ Bob Smith ┃ Marketing ┃
┃ Carol Davis ┃ Engineering ┃
┃ David Wilson ┃ Sales ┃
┃ Eve Brown ┃ Design ┃
┗━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
JSONL with arrays (each line contains an array that gets flattened):
tablo -f demo/data/users-array.jsonl --select name,activeOutput:
┏━━━━━━━━━┳━━━━━━━━┓
┃ name ┃ active ┃
┣━━━━━━━━━╋━━━━━━━━┫
┃ Alice ┃ true ┃
┃ Bob ┃ false ┃
┃ Carol ┃ true ┃
┃ David ┃ true ┃
┗━━━━━━━━━┻━━━━━━━━┛
Command:
tablo -i '[1,2,3,4]' --limit 3 --style markdownOutput:
| VALUE |
| ----- |
| 1 |
| 2 |
| 3 |Command:
tablo -i '[{"name":"Charlie","age":35},{"name":"Alice","age":30},{"name":"Bob","age":25}]' --sort ageOutput:
┏━━━━━┳━━━━━━━━━┓
┃ age ┃ name ┃
┣━━━━━╋━━━━━━━━━┫
┃ 25 ┃ Bob ┃
┃ 30 ┃ Alice ┃
┃ 35 ┃ Charlie ┃
┗━━━━━┻━━━━━━━━━┛
You can specify sort direction for each column individually using + (ascending) or - (descending) prefixes:
# Sort by department (ascending), then by age (descending)
tablo -f data.json --sort 'department,-age'
# Explicit ascending prefix (same as no prefix)
tablo -f data.json --sort '+name,-salary'
# Mixed directions with multiple columns
tablo -f data.json --sort 'active,-salary,name'Notes:
--sort 'column1,column2'sorts by multiple columns in order--sort '+column1,-column2'sorts column1 ascending, column2 descending- Works with flattened paths (e.g.,
--sort 'user.name,-user.age')
Sort rows using the --sort flag with column names:
--sort 'name'- sort by a single column--sort 'name,age'- sort by multiple columns (comma-separated)
Sorting supports different data types:
- Numbers: sorted numerically (e.g., 1, 2, 10, 100)
- Strings: sorted alphabetically
- Booleans: false comes before true
- Mixed types: fall back to string comparison
- Null values: always sorted first
This works with flattened paths when using --dive.
Example:
tablo -f employees.json --sort 'department,age' --select 'name,department,age'Output:
┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━┓
┃ name ┃ department ┃ age ┃
┣━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━┫
┃ Bob ┃ Engineering ┃ 25 ┃
┃ Charlie ┃ Engineering ┃ 35 ┃
┃ David ┃ Marketing ┃ 28 ┃
┃ Alice ┃ Marketing ┃ 30 ┃
┗━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━┛
Export data as CSV for use in spreadsheet applications:
tablo -i '[{"name":"John","age":30},{"name":"Jane","age":25}]' --style csvOutput:
age,name
30,John
25,Jane
Generate HTML tables for use in web applications:
echo '{"user":"admin","active":true}' | tablo --dive --style htmlOutput:
<table class="go-pretty-table">
<thead>
<tr>
<th>KEY</th>
<th>VALUE</th>
</tr>
</thead>
<tbody>
<tr>
<td>active</td>
<td>true</td>
</tr>
<tr>
<td>user</td>
<td>admin</td>
</tr>
</tbody>
</table>Filter rows using the --where flag with condition expressions:
--where 'name=John'- equality comparison--where 'age>25'- numeric comparison (>,>=,<,<=)--where 'active=true'- boolean comparison--where 'name~pattern'- string contains (~for contains,!~for not contains)--where 'email=~.*@example\.com'- regex matching (=~for match,!=~for not match)
Multiple --where flags are combined using AND logic. This works with flattened paths when using --dive.
Example:
tablo -f employees.json --where 'department=Engineering' --where 'salary>75000' --select 'name,salary'Output:
┏━━━━━━━━━┳━━━━━━━━┓
┃ name ┃ salary ┃
┣━━━━━━━━━╋━━━━━━━━┫
┃ Bob ┃ 85000 ┃
┃ Charlie ┃ 80000 ┃
┃ Frank ┃ 90000 ┃
┗━━━━━━━━━┻━━━━━━━━┛
You can customize formatting when rendering rows:
--bool-str 'Y:N'to render booleans as custom strings.--precision 2to format floats with 2 decimal places.--null-str nullto display missing values as the literalnull.
Example:
tablo -i '[{"a":1.2345,"b":true},{"b":false}]' --style ascii --precision 2 --bool-str 'Y:N' --index-columnOutput:
+---+------+---+
| | a | b |
+---+------+---+
| 1 | 1.23 | Y |
| 2 | null | N |
+---+------+---+
--diveenables flattening of nested objects and arrays of objects.--dive-path k1 --dive-path k2dives only into the listed top-level keys.
--max-depth Nlimits flattening depth (-1= unlimited).
Choose a table style with --style:
heavy(default),light,double,ascii,markdown,compact,borderless,html,csv.- Force ASCII borders with
--ascii(applies only to table styles).
Use dotted path expressions with glob support for each segment (* and ?). Examples:
- Include:
--select 'user.*.name,meta.id' - Exclude:
--exclude 'debug.*' - Strict mode:
--strict-selectfails if any selected path is missing.
- Stable releases are tagged with semantic versions:
vMAJOR.MINOR.PATCH. - Binaries built from an exact tag report that tag (e.g.,
v0.4.0). - Non-tag builds report a development identifier:
dev-<short-hash>. - A
-dirtysuffix is appended if there are uncommitted changes.
To create a new release:
# ensure clean working tree and tests pass
make ci
# choose the next version and create a tag
make TAG=v0.5.0 tag
# build multi-platform artifacts (automatically detects version from tag)
make release
The release process:
make tagvalidates the working tree is clean and creates/pushes the git tagmake releaserunsrelease-checkto validate git state and builds for multiple platforms- Release artifacts are built for: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, and windows/amd64.
- All binaries are placed in
dist/along with asha256sums.txtfile.