pytablewriter: one table in twenty output formats from one Python call
pytablewriter is a Python library to write a table in various formats: AsciiDoc / CSV / Elasticsearch / HTML / JavaScript / JSON / LaTeX / LDJSON / LTSV / Markdown / MediaWiki / NumPy / Excel / Pandas / Python / reStructuredText / SQLite / TOML / TSV.
At a glance
- What is it?
- pytablewriter turns a list of rows into Markdown, LaTeX, HTML, Excel, SQLite, JSON, reStructuredText and a dozen other formats through a family of writer classes. It handles cell formatting, alignment and padding so you stop hand-rolling pipes, and the optional dependencies mean you only install what the formats you use need.
- Who is it for?
- Adopt pytablewriter when you need the same tabular data rendered as Markdown, LaTeX, HTML or a machine readable format from one place, especially in a docs pipeline or a report generator. Do not adopt it if you need to read tables, since that is a different library, and do not expect it to produce a styled spreadsheet on its own beyond the formatting options it lists.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 88 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Twenty formats, one writer class per format
pytablewriter is a Python library to write a table in various formats, and the list in the project description is long enough to be the feature: AsciiDoc, CSV, Elasticsearch, HTML, JavaScript, JSON, LaTeX, LDJSON, LTSV, Markdown, MediaWiki, NumPy, Excel, Pandas, Python, reStructuredText, SQLite, TOML and TSV, with YAML added in the summary section of the documentation.
The design is one writer class per format, all following the same pattern. You import the class for the format you want, hand it a table name, a list of headers and a matrix of values, and call write_table().
The mechanism underneath is what makes the format count cheap rather than a maintenance burden. The same core handles the things that are genuinely fiddly in hand-written tables: automatic alignment, padding, and the number of decimal places used for numeric cells. Column widths line up because one place computes them.
There is a second layer of control on top. You can customise cell styles, including text and background colour, text alignment, font size and weight, and thousand separators for numbers in the 1,000 or 1 000 form. The output side is equally flexible: write a table to a stream such as a file, standard output, a string buffer or a Jupyter Notebook, or get the rendered tabular text back as a value.
The data sources are broader than the writer names suggest. A nested list works, CSV works, and pandas DataFrame and Series work, with the latter two mentioned explicitly in the data sources list. Multibyte character support and ANSI colour support are both called out, which matters for a library whose output is meant to be read in a terminal and to contain non-ASCII text.
Installing the base package and only the extras you need
The base install is a single pip command:
pip install pytablewriterThe formats that need extra packages are separated out, and the documentation gives an explicit table of them. The extras are es for Elasticsearch, excel for Excel, html for HTML, sqlite for a SQLite database, toml for TOML, theme for pytablewriter theme plugins, and all to install every optional dependency at once.
That packaging is a real design decision rather than an accident. A library that supports Excel and Elasticsearch must depend on the client libraries for both, and if those are hard dependencies, every user pays for them whether or not they want a spreadsheet. Splitting them into extras keeps the base install small and makes the dependency footprint of your own project legible.
There are two other installation routes. Conda is supported through conda-forge:
conda install -c conda-forge pytablewriterAnd there is an apt route through the maintainer's own PPA:
sudo add-apt-repository ppa:thombashi/ppa
sudo apt update
sudo apt install python3-pytablewriterThe extras are also visible in the packaging source. The setup.py file declares the Excel requirements as xlwt plus XlsxWriter with a version range, the Elasticsearch requirement as elasticsearch at 8.0.1 or later and below 9, the HTML requirement as dominate, the SQLite requirement as SimpleSQLite, and a separate from extra for reading with pytablereader.
That last one is a useful clue about the project's shape. pytablewriter is the writing half, and pytablereader is the reading half, and they are separate packages that happen to share an author and a data model.
A first table, and what the output actually looks like
The documented example writes a Markdown table from a small matrix with a mix of types, and it is a good illustration because the type mix is where a naive implementation falls down.
The sample code looks like this:
from pytablewriter import MarkdownTableWriter
def main():
writer = MarkdownTableWriter(
table_name="example_table",
headers=["int", "float", "str", "bool", "mix", "time"],
value_matrix=[
[0, 0.1, "hoge", True, 0, "2017-01-01 03:04:05+0900"],
[2, "-2.23", "foo", False, None, "2017-12-23 45:01:23+0900"],
[3, 0, "bar", "true", "inf", "2017-03-03 33:44:55+0900"],
[-10, -9.9, "", "FALSE", "nan", "2017-01-01 00:00:00+0900"],
],
)
writer.write_table()
if __name__ == "__main__":
main()The value matrix deliberately contains the awkward cases. A number arrives as a string, a boolean arrives as the string "true" in one row and "FALSE" in another, one cell is None, another is the string "inf", another is the string "nan", one cell is an empty string, and the timestamps include impossible values like 45:01:23 and 33:44:55.
The result shows what the library decided to do with them:
# example_table
|int|float|str |bool | mix | time |
|--:|----:|----|-----|-------:|------------------------|
| 0| 0.10|hoge|True | 0|2017-01-01 03:04:05+0900|
| 2|-2.23|foo |False| |2017-12-23 12:34:51+0900|
| 3| 0.00|bar |True |Infinity|2017-03-03 22:44:55+0900|
|-10|-9.90| |False| NaN|2017-01-01 00:00:00+0900|The interesting parts are the columns. The alignment row, with the colons marking right alignment for the numeric columns, is generated rather than typed. The numeric columns are padded and the floats are rendered to two decimal places, so 0 becomes 0.00 and -9.9 becomes -9.90. The string "true" became True and "FALSE" became False, so the boolean column is normalised rather than passed through. The mix column is the interesting one: the string "inf" became Infinity, "nan" became NaN, and the None became an empty cell.
The timestamps were also corrected. The impossible hour and minute values in the source were normalised to 12:34:51 and 22:44:55, and the offset on the first row was preserved as +0900. That normalisation is worth understanding rather than assuming, because it means the library is interpreting your data as dates and rewriting parts of it.
Where the output is aimed, from Markdown to Elasticsearch
The format list is not arbitrary, and the grouping in the feature list tells you which formats are treated as text, which as binary files, and which as application specific.
The text formats are the bulk. AsciiDoc, CSV and its tab and space separated variants, HTML and CSS, JSON and line-delimited JSON, LTSV, LaTeX in the tabular and array environments, Markdown in CommonMark, GitHub Flavored Markdown and kramdown flavours, MediaWiki, and reStructuredText in the Grid, Simple and CSV Table forms. There is also a source code category, where the output is a definition of a variable holding the data, in JavaScript, NumPy with numpy.array, Pandas with pandas.DataFrame, or plain Python. TOML, YAML and Unicode complete the text list.
The binary formats are Microsoft Excel in the xlsx and xls file formats, a pandas.DataFrame pickle file, and a SQLite database file. That last one is the format worth pausing on, because a SQLite database is not a rendering of a table but a working database, with a schema created and rows inserted. For a project that needs to ship a small relational dataset, that saves writing a schema and a set of inserts by hand.
The application specific format is Elasticsearch, which is a bulk indexing payload rather than a document. The es extra pulls in the elasticsearch client, and the version range is 8.0.1 up to but not including 9.
The variety of dialects within a single format is the detail that shows the project's care. Markdown is not one thing but three, reStructuredText is not one thing but three, and LaTeX offers two environments. Getting the CommonMark table right rather than the GFM table, or the Simple Table rather than the Grid Table, is exactly the kind of detail that causes a documentation build to fail or a wiki page to render wrong, and it is the reason to use a library like this instead of formatting by hand.
The project layout, the tooling and the release cadence
The repository is a long-running project by Tsuyoshi Hombashi, with the first release year recorded as 2016 in the Makefile. The last push was on 2026-07-27, and the released versions are v1.2.1 on 2025-01-01, v1.2.0 on 2023-10-08 and v1.1.0 on 2023-09-24.
That spacing is worth noting. A gap of about two years between v1.2.0 and v1.2.1 in a project that has been running for a decade is a signal that the maintenance level is steady rather than busy. The work continues, and the last push in July 2026 shows the tree is still active, but the version numbers do not move quickly.
The repository layout follows the pattern of a project with a large documentation effort. There is a pytablewriter directory for the package, an ss directory that appears to hold the screenshot assets, an examples directory split into ipynb and py, a docs directory, a requirements directory holding the requirements files that setup.py reads, and a test directory. A .readthedocs.yaml sits at the root, and the documentation is published at pytablewriter.rtfd.io.
The tooling is thorough in a way that most library repositories are not. The build uses setuptools with setuptools_scm for versioning, so the version comes from the git history rather than a hardcoded string. The formatting is black with a line length of 100 and target versions from py310 through py314, and isort is configured with a known third party list that includes pandas, pytablereader, tabledata, typepy and yaml. The type checking is mypy with a fairly strict set of flags, including disallow_untyped_defs and disallow_incomplete_defs, alongside a pyright configuration. Coverage runs in branch mode with a set of excluded lines, and pytest is configured to run the test directory.
The Makefile shows the release machinery, including a target that installs a Go tool to generate the changelog from the release history, and a build-remote target that clones the repository fresh and builds it with tox. CI runs on Linux, macOS and Windows, and there is a CodeQL workflow alongside it.
Editorial conclusion
Adopt pytablewriter when you need the same tabular data rendered as Markdown, LaTeX, HTML or a machine readable format from one place, especially in a docs pipeline or a report generator. Do not adopt it if you need to read tables, since that is a different library, and do not expect it to produce a styled spreadsheet on its own beyond the formatting options it lists. Before you install, pick the extras that match the formats you actually use, such as pytablewriter[excel] or pytablewriter[html], rather than taking the all extra, and check the Markdown output sample in the documentation since the alignment markers in the generated table are what reviewers will notice.
Frequently asked questions
How do I install pytablewriter and the dependencies for Excel or HTML output?
Install the base package with pip install pytablewriter, then add the extra for the formats you need, such as pytablewriter[excel] for Excel, pytablewriter[html] for HTML, pytablewriter[es] for Elasticsearch, pytablewriter[sqlite] for a SQLite database, pytablewriter[toml] for TOML, pytablewriter[theme] for theme plugins, or pytablewriter[all] for everything.
How do I write a Markdown table with pytablewriter?
Instantiate MarkdownTableWriter with a table_name, a headers list and a value_matrix, then call write_table(). The library generates the alignment markers, pads the columns and formats the numeric cells for you.
Which table formats does pytablewriter support?
Among them AsciiDoc, CSV, TSV, SSV, HTML, CSS, JSON, LDJSON, LTSV, LaTeX, Markdown in CommonMark, GitHub Flavored Markdown and kramdown, MediaWiki, reStructuredText, TOML, YAML, Unicode, JavaScript, NumPy, Pandas, Python, Excel, a pandas pickle file, a SQLite database and an Elasticsearch payload.
Can pytablewriter write into a database rather than a text file?
Yes. SQLite is one of the binary file formats it supports, so it can produce a SQLite database file, and the sqlite extra installs the SimpleSQLite package it needs. It also writes Excel xlsx and xls files and pandas DataFrame pickle files.
What data can I feed to pytablewriter?
The documented data sources are a nested list, CSV, and pandas DataFrame or Series, with etc. indicating more are accepted. The writer can then output to a file, standard output, a string buffer or a Jupyter Notebook.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thombashi-pytablewriter)Was this useful?