easy-table
easy-table is a small CommonJS formatter for aligned plain-text tables in Node.js command-line programs. You add named cells to a current row, close the row, then render headers, delimiter lines, body rows, and optional totals. It can also print arrays or objects directly, sort rows, transpose a record into key/value lines, right-align fixed-decimal numbers, and use custom two-pass cell printers. Width calculation strips ANSI escape sequences and optionally uses wcwidth for wide Unicode characters.
easy-table still does clean, borderless reports with very little code, but its long release gap makes it a maintenance-risk choice for new projects. Pick it only when this exact small API fits; choose `table` or `cli-table3` for richer output and more active stewardship.
Use it if
- You need a dependency-light table string for a Node CLI and borders, colors, wrapping, and streaming are unnecessary
- Your rows are ordinary objects but you want column renaming and numeric alignment without building a formatter
- You need built-in sorting and sum or average footer rows for a small report
- You maintain CommonJS code and value an API small enough to understand from one README and one source file
- You want an actively evolving dependency: version 1.2.0 was published in October 2021 and the repository's last push was in February 2025, with no newer release
- Your project is strict ESM: the package exposes only a CommonJS main entry and uses `export =` in its bundled declarations, so interop depends on your compiler or runtime configuration
- You need wrapped cells, multiline rows, spans, borders, or color themes: the implementation renders one physical line per row and only lets you change the inter-column separator
- You print untrusted or irregular objects: Table.print enumerates own enumerable keys and stringifies values, so nested objects become `[object Object]` and getters may run
- Exact alignment across every Unicode terminal is critical: wcwidth is an optional dependency, and without it width falls back to JavaScript string length even though ANSI codes are stripped
Setup reality
`npm install easy-table` provides one CommonJS module, a declaration file, `ansi-regex`, and optional `wcwidth`. There is no initialization or config file. In CommonJS use `const Table = require('easy-table')`; TypeScript's bundled `export = EasyTable` shape works naturally with `import Table = require('easy-table')`, while default ESM imports depend on `esModuleInterop` or your runtime's CommonJS bridge. Rows are stateful. Calling `cell()` only fills the current row, and you must call `newRow()` or nothing is committed for output. Missing columns render blank, while column order is inferred from the order keys first appear across committed rows. Every custom printer runs twice: once without a width to measure, then again with the chosen column width to render. It must be deterministic and side-effect-free, and it should return a string both times. `Table.number(digits)` rejects non-number values but renders null and undefined as blank; convert numeric strings before adding them. ANSI escape sequences are removed for width measurement, but display-width accuracy for CJK and other wide characters relies on optional `wcwidth`. The formatter does not wrap or sanitize newlines, so multiline values break the one-row-per-line layout. `toString()` adds headings and dashed separators, `print()` emits body rows only, and `printTransposed()` turns columns into rows. The static `Table.print()` overload behaves differently for arrays and single objects: arrays become a normal headed table, while one object becomes a transposed key/value view. Sorting mutates the stored row order. Descending keys use the library's documented `|des` suffix, not the more obvious `|desc`. Totals are computed at render time from raw stored values, so mixing missing values or strings with the default sum reducer can produce unwanted coercion.
Patterns
Build a table row by rowrender-basic-table
const Table = require('easy-table')
const table = new Table()
for (const user of users) {
table.cell('ID', user.id)
table.cell('Name', user.name)
table.cell('Active', user.active ? 'yes' : 'no')
table.newRow()
}
process.stdout.write(table.toString())cell only updates the current row. Call newRow after every record or that record will not appear in the rendered table.
Right-align fixed-decimal valuesformat-decimal-numbers
const table = new Table()
for (const invoice of invoices) {
table.cell('Invoice', invoice.id)
table.cell('Amount', invoice.amount, Table.number(2))
table.newRow()
}
console.log(table.toString())Table.number accepts actual numbers. Numeric strings throw; null and undefined are rendered as blank cells.
Render an array without manual rowsprint-object-array
const output = Table.print(products, {
sku: { name: 'SKU' },
description: { name: 'Description' },
price: { name: 'Price', printer: Table.number(2) },
})
console.log(output)Static print enumerates each object's own enumerable keys. Normalize nested values before passing domain objects directly.
Render one object as key/value rowsprint-key-value-object
console.log(Table.print({
version: '1.4.0',
environment: 'production',
healthy: true,
}))A single object is printed transposed with ` : ` separators, unlike an array, which gets a normal header row.
Omit headers and delimiter rowsrender-body-only
const table = new Table()
for (const row of rows) {
table.cell('left', row.label)
table.cell('right', row.value)
table.newRow()
}
process.stdout.write(table.print())Instance print emits committed body rows only. Use toString when you want column headings and dashed separators.
Customize spacing between columnschange-column-separator
const table = new Table()
table.separator = ' | '
table.cell('Name', 'api').cell('State', 'ready').newRow()
table.cell('Name', 'worker').cell('State', 'busy').newRow()
console.log(table.toString())separator is the only built-in border control. easy-table does not draw vertical or outer borders for you.
Sort by multiple stored columnssort-table-rows
table
.sort(['Status|asc', 'Duration ms|des'])
.log()Sorting mutates the table's rows. The descending suffix is `|des`, exactly as documented, not `|desc`.
Apply a custom row comparatorsort-with-comparator
table.sort((a, b) => {
const severity = { error: 0, warning: 1, info: 2 }
return severity[a.Level] - severity[b.Level]
})
console.log(table.toString())Comparator arguments are the internal row objects keyed by the exact column labels passed to cell.
Add a sum footeradd-column-total
const table = new Table()
for (const item of items) {
table.cell('Item', item.name)
table.cell('Cost', item.cost, Table.number(2))
table.newRow()
}
table.total('Cost', { printer: Table.number(2) })
console.log(table.toString())The default reducer uses addition on raw cell values. Missing or string costs can coerce the accumulator unexpectedly.
Render an average with a labeladd-average-footer
table.total('Latency', {
reduce: Table.aggr.avg,
init: 0,
printer: Table.aggr.printer('Avg: ', Table.number(1)),
})
console.log(table.toString())Totals appear only in toString output and only when at least one committed row exists.
Create a deterministic cell printerwrite-custom-printer
function percent(value, width) {
const text = `${(value * 100).toFixed(1)}%`
return width ? Table.padLeft(text, width) : text
}
table.cell('Success', 0.973, percent).newRow()A printer is called twice: first without width for measurement, then with width for rendering. Avoid side effects and return the same content shape.
Import the CommonJS package in TypeScriptuse-from-typescript
import Table = require('easy-table')
const table = new Table()
table.cell<number>('Count', 42, Table.number(0)).newRow()
console.log(table.toString())The bundled declarations use `export = EasyTable`. A default import needs esModuleInterop or an equivalent bundler setting.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| cli-table3 | npm | You want borders, colored cells, spanning, wrapping, and a maintained fork of the classic cli-table API |
| table | npm | You need configurable borders, alignment, column widths, truncation, and word wrapping |
| ascii-table | npm | You want a simple bordered ASCII table and can accept another old, minimal package |
| cli-table | npm | You inherit code already using the original cli-table API and cannot migrate immediately |