A high-performance table rendering library for Go, with streaming APIs for every output format.
Table of contents
- Table of contents
- Overview
- Motivation
- Output formats
- Text output gallery
- Installation
- Quick start
- Runnable examples
- Table library comparison
- Performance
- Documentation
- Author
- License
Overview
nekrassov01/table renders Go data as terminal tables, markup tables, or CSV records. Each output package provides Table for complete data sets and Stream for row-at-a-time output. Both preserve the selected format's structure and escaping rules.
See Runnable examples for a generated catalog of inputs, options, commands, and exact output.
tableuses functional options for clear, reusable configuration.- In the bundled comparisons,
tableruns 6.3 to 6.7 times as fast as the next-fastest alternative; see Performance. tablereuses internal buffers to minimize steady-state allocations.TableOfandStreamOfadapt typed slices and error-returning iterators.textmeasures Unicode by terminal display width, including ambiguous character widths in CJK locales.- Format-specific options add headers, calculated footers, and placeholders. They also support transformations, alignment, decoration, and cell spans.
Motivation
The project was created for four reasons:
- To provide a table renderer that is fast, efficient, and easy to use.
- To provide row-at-a-time output across every format, which no comparable library offered at the time.
- To manage table-oriented output formats for applications such as CLIs in one module.
- To support the less common Backlog table notation, which only the author's earlier
mintabproject covered at the time.
Output formats
Choose an output package for the destination. The root table package provides shared interfaces, typed row adapters, and errors; it does not select an output format.
| Package | Output | Use it for |
|---|---|---|
text |
Unicode or ASCII bordered tables | CLIs, terminals, and logs |
html |
Semantic HTML tables | Web pages and reports |
markdown |
GFM tables | READMEs and GitHub |
backlog |
Backlog table notation | Backlog issues and Wiki pages |
csv |
CSV records with a configurable delimiter | TSV, CSV, and data interchange |
Text output gallery
The examples below show the text package in several configurations.
ASCII
Simple
Compact layout with horizontal lines omitted and a colored rounded border style
Row spans with a colored heavy border style
Column spans with a colored light border style
Stacked header with a colored light border style
Calculated footer with CJK text and a colored light border style
Calculated footer with CJK text, value transformations, and a colored light border style
Complex values with a colored double border style
Installation
Install with:
go get github.com/nekrassov01/table
Quick start
TableOf and StreamOf adapt typed application data to rows of table.Value. Primitive cell constructors avoid interface boxing. Transformers also receive table.Value and can use typed accessors such as AsInt(). See Value inputs and migration for input types and transformer behavior.
Table
Use TableOf to keep application data typed until the row boundary. This example renders deployment status to a terminal.
package main import ( "log" "os" "github.com/nekrassov01/table" "github.com/nekrassov01/table/text" ) type Deployment struct { Service string Desired int Ready int Status string } func deploymentRow(deployment Deployment) []table.Value { return []table.Value{ table.String(deployment.Service), table.Int(deployment.Desired), table.Int(deployment.Ready), table.String(deployment.Status), } } func main() { deployments := []Deployment{ {Service: "payments", Desired: 4, Ready: 4, Status: "healthy"}, {Service: "search", Desired: 3, Ready: 2, Status: "degraded"}, {Service: "worker", Desired: 8, Ready: 8, Status: "healthy"}, } output := text.NewTable(os.Stdout, text.WithHeader([]string{"SERVICE", "DESIRED", "READY", "STATUS"}), text.WithAlign(text.ScopeBody, text.Columns(1, 2), text.AlignRight), text.WithCompact(), ) if err := output.Render(table.TableOf(deployments, deploymentRow)); err != nil { log.Fatal(err) } }
This program produces the following table:
┌──────────┬─────────┬───────┬──────────┐
│ SERVICE │ DESIRED │ READY │ STATUS │
╞══════════╪═════════╪═══════╪══════════╡
│ payments │ 4 │ 4 │ healthy │
│ search │ 3 │ 2 │ degraded │
│ worker │ 8 │ 8 │ healthy │
└──────────┴─────────┴───────┴──────────┘
Stream
Use StreamOf to adapt an iter.Seq2[T, error] to streaming table rows. Each successful value becomes one output row, and the first source error is forwarded. Call Stream.Close even after an earlier error so it can attempt deferred output and release its internal workspace.
package report import ( "io" "iter" "time" "github.com/nekrassov01/table" "github.com/nekrassov01/table/text" ) type AuditEvent struct { Time time.Time Actor string Action string Resource string } func WriteAuditEvents(w io.Writer, events iter.Seq2[AuditEvent, error]) (err error) { output := text.NewStream(w, text.WithHeader([]string{"TIME", "ACTOR", "ACTION", "RESOURCE"}), text.WithCompact(), ) defer func() { if closeErr := output.Close(); err == nil { err = closeErr } }() rows := table.StreamOf(events, func(event AuditEvent) []table.Value { return []table.Value{ table.String(event.Time.Format(time.RFC3339)), table.String(event.Actor), table.String(event.Action), table.String(event.Resource), } }) for row, sourceErr := range rows { if sourceErr != nil { return sourceErr } if renderErr := output.Render(row); renderErr != nil { return renderErr } } return nil }
Given an iterator of audit events, the function produces output like this:
┌───────────────────────────┬────────────┬───────────┬──────────────┐
│ TIME │ ACTOR │ ACTION │ RESOURCE │
╞═══════════════════════════╪════════════╪═══════════╪══════════════╡
│ 2026-08-21T09:00:00+09:00 │ deploy-bot │ reconcile │ payments-api │
│ 2026-08-21T09:03:00+09:00 │ alice │ scale │ worker │
│ 2026-08-21T09:08:00+09:00 │ bob │ rollback │ search-api │
└───────────────────────────┴────────────┴───────────┴──────────────┘
Runnable examples
The generated examples catalog pairs shared input data with the exact options, commands, and output for every supported scenario. The same definitions drive the catalog, runnable examples, and benchmarks.
Use target to select the output package and mode to select the API. Use data to select the scenario. This command runs the simple text Table example:
make example target=text mode=table data=simple
Omit data to run every scenario for the selected package and mode. With no data selected, omit mode to run both APIs, or run make example without arguments to run every available example.
Table library comparison
The following tables compare the public APIs in the versions pinned by the benchmark module.
Formats
This table records the output implementations documented by each library. ✓ means the library provides a dedicated output mode for the format, and - means it does not. table targets the GFM table extension; the other Markdown entries indicate generic Markdown table output.
| Output format | table |
go-pretty v6.8.3 |
tablewriter v1.1.5 |
simpletable v1.0.0 |
|---|---|---|---|---|
| Text | ✓ | ✓ | ✓ | ✓ |
| HTML | ✓ | ✓ | ✓ | - |
| Markdown | ✓ | ✓ | ✓ | ✓ |
| Backlog notation | ✓ | - | - | - |
| CSV or TSV | ✓ | ✓ | - | - |
| SVG | - | - | ✓ | - |
Features
This table records whether each library exposes a direct public API for a capability in at least one output implementation. It does not imply that every format can express the capability.
| Feature | table |
go-pretty v6.8.3 |
tablewriter v1.1.5 |
simpletable v1.0.0 |
|---|---|---|---|---|
| Typed value input | ✓ | - | - | - |
| Caller-defined row adapter | ✓ | - | ✓ | - |
| Reflection-based struct input | - | - | ✓ | - |
| CSV input | - | - | ✓ | - |
| Per-column transformation | ✓ | ✓ | ✓ | - |
| Built-in sorting and filtering | - | ✓ | - | - |
| Column hiding | - | ✓ | ✓ | - |
| Header | ✓ | ✓ | ✓ | ✓ |
| Footer | ✓ | ✓ | ✓ | ✓ |
| Index column | - | ✓ | - | - |
| Vertical merge | ✓ | ✓ | ✓ | - |
| Horizontal merge | ✓ | ✓ | ✓ | ✓ |
| Placeholder | ✓ | ✓ (HTML) | - | - |
| Width, wrapping, and truncation | ✓ | ✓ | ✓ | - |
| Automatic terminal fit | ✓ | - | - | - |
| Title or caption | ✓ | ✓ | ✓ | - |
| Streaming API | ✓ (All) | - | ✓ | - |
| Pagination | - | ✓ | - | - |
| Pluggable output implementation | - | - | ✓ | - |
Typed value input means constructors such as table.String() and table.Int() preserve primitive types without interface boxing. table.Any() remains available for arbitrary values. See Value inputs and migration.
The compared versions use these input APIs:
go-prettydefinesRowas[]interface{}.tablewriteracceptsAppend(...interface{})andBulk(interface{}).simpletableaccepts strings throughCell.Text. Callers must format numeric values before assigning them.
For table, the feature matrix has the following qualifications:
- Column hiding is intentionally left to input adaptation, so
TableOfandStreamOfcan omit fields before rows reach the output package. - Footer callbacks derive values such as totals and averages from captured state.
- Merge behavior depends on the selected output format and is documented in the Public API guide.
The go-pretty placeholder entry refers to its HTML EmptyColumn setting.
Performance
Note
table is designed to minimize allocations and reuses internal buffers via sync.Pool. In steady-state benchmarks, common workloads reach one allocation per render; cold runs require additional allocations to initialize pooled state.
Run make bench target=comparison benchtime=1x count=1 to reduce steady-state amortization and expose one-iteration setup costs. For explicit pool-drained measurements of table, run make bench target=cold.
Run the comparison on your machine with make bench target=comparison benchtime=10000x count=5 cpuprofile= memprofile=. The following output records all five samples at commit 49a56df on an Apple M2 with Go 1.27.1, with profiling disabled:
$ make bench target=comparison benchtime=10000x count=5 cpuprofile= memprofile=
go test -benchmem -count 5 -benchtime 10000x . -bench '^BenchmarkComparison'
goos: darwin
goarch: arm64
pkg: benchmarks
cpu: Apple M2
BenchmarkComparisonTableSimple-8 10000 3129 ns/op 210 B/op 1 allocs/op
BenchmarkComparisonTableSimple-8 10000 2157 ns/op 208 B/op 1 allocs/op
BenchmarkComparisonTableSimple-8 10000 1834 ns/op 208 B/op 1 allocs/op
BenchmarkComparisonTableSimple-8 10000 1696 ns/op 208 B/op 1 allocs/op
BenchmarkComparisonTableSimple-8 10000 1657 ns/op 208 B/op 1 allocs/op
BenchmarkComparisonGoPrettySimple-8 10000 10320 ns/op 8152 B/op 110 allocs/op
BenchmarkComparisonGoPrettySimple-8 10000 10697 ns/op 8152 B/op 110 allocs/op
BenchmarkComparisonGoPrettySimple-8 10000 10836 ns/op 8152 B/op 110 allocs/op
BenchmarkComparisonGoPrettySimple-8 10000 10369 ns/op 8152 B/op 110 allocs/op
BenchmarkComparisonGoPrettySimple-8 10000 11590 ns/op 8152 B/op 110 allocs/op
BenchmarkComparisonTableWriterSimple-8 10000 89390 ns/op 486951 B/op 973 allocs/op
BenchmarkComparisonTableWriterSimple-8 10000 88622 ns/op 486951 B/op 973 allocs/op
BenchmarkComparisonTableWriterSimple-8 10000 90418 ns/op 486951 B/op 973 allocs/op
BenchmarkComparisonTableWriterSimple-8 10000 88937 ns/op 486951 B/op 973 allocs/op
BenchmarkComparisonTableWriterSimple-8 10000 91323 ns/op 486951 B/op 973 allocs/op
BenchmarkComparisonSimpleTableSimple-8 10000 23144 ns/op 13082 B/op 425 allocs/op
BenchmarkComparisonSimpleTableSimple-8 10000 24135 ns/op 13089 B/op 425 allocs/op
BenchmarkComparisonSimpleTableSimple-8 10000 23233 ns/op 13075 B/op 425 allocs/op
BenchmarkComparisonSimpleTableSimple-8 10000 23404 ns/op 13082 B/op 425 allocs/op
BenchmarkComparisonSimpleTableSimple-8 10000 22597 ns/op 13071 B/op 425 allocs/op
BenchmarkComparisonTableComplex-8 10000 9278 ns/op 1133 B/op 35 allocs/op
BenchmarkComparisonTableComplex-8 10000 9216 ns/op 1133 B/op 35 allocs/op
BenchmarkComparisonTableComplex-8 10000 9175 ns/op 1133 B/op 35 allocs/op
BenchmarkComparisonTableComplex-8 10000 9136 ns/op 1133 B/op 35 allocs/op
BenchmarkComparisonTableComplex-8 10000 9209 ns/op 1133 B/op 35 allocs/op
BenchmarkComparisonGoPrettyComplex-8 10000 63260 ns/op 49250 B/op 317 allocs/op
BenchmarkComparisonGoPrettyComplex-8 10000 60747 ns/op 49251 B/op 317 allocs/op
BenchmarkComparisonGoPrettyComplex-8 10000 60804 ns/op 49251 B/op 317 allocs/op
BenchmarkComparisonGoPrettyComplex-8 10000 62962 ns/op 49251 B/op 317 allocs/op
BenchmarkComparisonGoPrettyComplex-8 10000 60516 ns/op 49249 B/op 317 allocs/op
BenchmarkComparisonTableWriterComplex-8 10000 301172 ns/op 720130 B/op 4749 allocs/op
BenchmarkComparisonTableWriterComplex-8 10000 296691 ns/op 720124 B/op 4749 allocs/op
BenchmarkComparisonTableWriterComplex-8 10000 295675 ns/op 720126 B/op 4749 allocs/op
BenchmarkComparisonTableWriterComplex-8 10000 299385 ns/op 720127 B/op 4749 allocs/op
BenchmarkComparisonTableWriterComplex-8 10000 297917 ns/op 720126 B/op 4749 allocs/op
PASS
ok benchmarks 25.122s
The table summarizes those five samples. Each cell shows allocs/op · ns/op · B/op; all values are medians.
| Scenario | table |
go-pretty |
tablewriter |
simpletable |
|---|---|---|---|---|
| Simple | 1 · 1,834 · 208 | 110 · 10,697 · 8,152 | 973 · 89,390 · 486,951 | 425 · 23,233 · 13,082 |
| Complex values | 35 · 9,209 · 1,133 | 317 · 60,804 · 49,251 | 4,749 · 297,917 · 720,126 | - |
- indicates that a library cannot express the scenario with the benchmark input.
The comparison benchmark uses the shared Simple and Complex data sets. Static data is converted to each library's required row type before timing begins. Each timed iteration constructs a table, processes the rows, and writes the result to a reused buffer. Complex compares native value handling rather than equivalent rendered bytes.
The benchmark preserves each library's configuration model. table uses only functional options, while go-pretty accumulates settings through setters. tablewriter combines constructor options with methods. simpletable receives prebuilt cells through exposed table sections. These native construction paths remain inside each timed iteration. Only the settings needed to align table structure and preserve header text are applied. Border characters and value formatting retain each library's defaults.
Documentation
Use these references to select the API, understand its design, and work on the module itself.
| Resource | Contents |
|---|---|
| Go Reference | Exact declarations and symbol documentation |
| Public API guide | Options, output behavior, defaults, and format capabilities |
| Architecture | Package structure, data flow, and state ownership |
| Design specification | Design decisions, invariants, tradeoffs, and non-goals |
| Development guide | Test, benchmark, coverage, and analysis commands |
| Performance baseline | Benchmark procedure and performance acceptance criteria |









