CSVtoTable: one binary that turns a data file into a self-contained HTML page
Simple command-line utility to convert CSV, TSV and Excel files into a single self-contained HTML page with search, filters, and sorting
At a glance
- What is it?
- CSVtoTable is a Go command line tool that converts CSV, TSV and Excel files into a single HTML page with search, filters and sorting, with the data, styles and scripts inlined so the file works offline. The interesting engineering is in the output modes, where a compressed self-unpacking page and a content hashed directory of assets solve opposite problems.
- Who is it for?
- Adopt CSVtoTable when you need to hand someone a spreadsheet view that opens from disk with no server, no network and no installation on their side, and skip it when the audience needs live data, because the page is a snapshot built at conversion time.
- 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 62 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The output is one file with the data, the styles and the script inside it
The starting point for CSVtoTable is a plain file conversion, but the point of the tool is the shape of the artifact it leaves behind. A single native binary carries embedded frontend assets, and by default the data, the styles and the scripts are inlined into one HTML page. The consequence is that the result is a standalone file: you can mail it, drop it on a share, open it from a USB stick or attach it to a ticket, and the filtering, sorting and search all still work, with no server and no network.
The feature list behind that page is substantial for a converter. Search runs across the table, per-column filters appear as clearable chips, sorting is available, pagination and virtual scrolling handle large row counts, and the exports include copy, CSV, JSON and print, alongside column show and hide. Five colour themes ship with the tool and can be switched inside the page or fixed at conversion time with a flag. There is also a live demo page built by the tool itself from sample data and served as a single static file, which is a quick way to see what the output looks like before converting anything of your own.
Input handling covers encodings, archives, workbooks and standard input
The input side is where a converter either copes with real files or does not, and this one covers a lot of ground. CSV, TSV and Excel workbooks in the .xlsx format are accepted, and gzip archives are included, so a compressed data file needs no separate decompression step. The source can be a local file, a URL, standard input, or several files combined into one table when the columns match. A byte order mark and UTF-16 are detected automatically, and the rest is handled with explicit flags for encoding, delimiter and quote character.
csvtotable input.csv page.html
csvtotable input1.csv input2.csv page.html
csvtotable data.csv.gz data.html
csvtotable sales.xlsx sales.html
csvtotable https://raw.githubusercontent.com/vividvilla/csvtotable/master/demo/meteorite-landings-1.csv meteorites.html
csvtotable data.csv --serve
csvtotable data.csv --serve :8080
csvtotable data.csv site/ --split
csvtotable data.csv data.html --title "Sales" --no-header
csvtotable data.csv data.html --page-size 50
csvtotable data.csv data.html --theme solarized
csvtotable data.csv data.html --css brand.css --js setup.js
curl -L https://example.com/data.csv | csvtotable - - > data.htmlThe last line is worth noticing, because reading standard input and writing standard output turns the tool into a filter that can sit in a shell pipeline. The same property is what makes it usable in a script that fetches a file and immediately converts it, with no temporary file in between.
Five ways to install it, from a throwaway run to a binary on your PATH
The distribution strategy covers the different levels of commitment people have towards a command line tool. The lightest option runs without installing at all, through uvx or through npx, and the next step is a real installation with pipx, uv tool install, a pip install inside a virtual environment, or npm install --global. Homebrew is covered with a tap, and for people who do not want a package manager involved at all there are prebuilt binaries on the releases page, with a SHA256SUMS file to verify them against.
uvx csvtotable data.csv data.html
pipx install csvtotable
uv tool install csvtotable
pip install csvtotable # inside a virtual environment
npx @vividvilla/csvtotable data.csv data.html # run without installing
npm install --global @vividvilla/csvtotable # or install globally
uvx [email protected] data.csv data.html
npx @vividvilla/[email protected] data.csv data.html
brew install vividvilla/tap/csvtotablePinning a published version is a first class operation in both the Python and the Node packaging, which tells you something about how the maintainer thinks about reproducibility. The prebuilt binaries cover Linux on x86-64 and ARM64, macOS 12 and newer on both architectures, and Windows 10 and newer on x86-64, so the platform coverage is broad but not universal, and there is no 32-bit build mentioned for Windows. The Go module path carries a version suffix and the project builds with Go 1.25, with the frontend built separately by bun and the Go binary linked with CGO disabled.
--split trades one file for cacheable assets with content hashed names
The default output and the split output exist for different situations, and the difference is worth understanding before you pick one. The default is a single self-contained page, which is unbeatable for a file you need to hand to one person. The split mode writes the output path as a directory instead of a page, with the page itself kept to a couple of kilobytes and the stylesheet, the frontend and the row data in separate files whose names carry a content hash.
That hashing is the point. An unchanged rerun keeps the same URL, so the browser cache still hits, and changed rows produce a new name that cannot be served stale. The page keeps the name index.html regardless, so its freshness is only as good as whatever serves it. Superseded files are left in place for you to clear out, which is a deliberate choice to avoid deleting something behind a server's back, and it means the directory grows across runs.
References between the files are relative, so the directory can be served from any path, and nothing in the page needs fetch, so index.html still opens straight from disk. The trade-off is that the CSS and JavaScript cannot be compressed in this mode, because caching is doing that job, and a CSS theme has to be inlined there rather than referenced, since the theme picker cannot find an external file over file://.
The compressed page trades older browsers for a smaller file
The frontend script ships gzipped and is then base64 encoded into the page, which cuts an otherwise empty file from about 260KB to 145KB. Unpacking that payload at runtime needs DecompressionStream, and the project is specific about the versions that have it: Chrome 103 and newer, Firefox 113 and newer, Safari 16.4 and newer. Browsers without it get a message saying so rather than a broken page, and the no-compress flag inlines readable source instead for anyone who needs the page to work everywhere or needs to read what it contains.
The stylesheet is deliberately left uncompressed in either mode, and the reason given is first paint. An uncompressed stylesheet can start styling the page before the script has been decompressed, so the table looks right early instead of showing an unstyled flash. The output size section of the documentation also gives a figure for the split mode, with the demo data costing roughly 105KB on the first visit and 31KB on a revisit behind a gzipping server, which is the number that matters for a deployed site.
The page is plain semantic HTML with csvtotable- class hooks
The styling contract is explicit, which is unusual and useful for a tool that generates pages you might have to restyle. The page is plain semantic HTML, and every element the tool owns carries a class prefixed with csvtotable-. The documented hooks include the main element as the page root, a header wrapping the title and description, the title heading, a description block, the table element, the theme picker and the filters area. Those class names are described as the stable hooks, so a custom stylesheet can target them without relying on the internal structure of the generated markup.
Your own styles and scripts are inlined with dedicated flags, and the live table API is exposed to them, so a page can be extended with behaviour the converter does not implement. The themes themselves are just CSS variables, which is why a theme defined with your own CSS file joins the picker rather than replacing it. The five built-in themes can be fixed at conversion time with the theme flag or left switchable inside the page.
Titles and descriptions accept Markdown, with a syntax for reading the description from a file, and there are parallel flags for raw HTML when you need markup that Markdown cannot express.
Version 2 flags still work, which tells you how the tool has been changing
The project keeps a compatibility surface for its own earlier releases, and the documentation is candid about it. The caption, display-length, pagination and export flags still work for compatibility with version 2, and the last two disable the features they replaced, so a command line carried over from the older version keeps running but quietly loses the newer pagination and export behaviour. That is a reasonable migration path, but it is a silent one, which means an old script that now does less than it used to will not tell you.
The release pattern shows a tool in active development. The tags are v3.1.0 and v3.2.0 on 2026-08-21 and v3.2.1-beta.1 on 2026-08-24, with the last push on 2026-08-24. The current package metadata carries the version 3.2.1b1 and the licence MIT, which is a permissive licence allowing use in closed source products, provided the notice travels with copies of the software. A beta tag as the newest release is worth weighing: the tool is moving fast, and the version you will get from a package manager by default is the beta rather than a settled release, so pinning a version with the syntax shown earlier is the safer habit for a tool you intend to depend on in a pipeline.
Where a spreadsheet viewer or a data grid would fit instead
The realistic alternatives come in two shapes, and both differ from CSVtoTable in what owns the data. A spreadsheet application opens the source file directly and stays live, so an edit is saved back and the file remains the working copy, with no conversion step and no snapshot. A hosted grid component does the same in the browser, loading rows from a live source and letting the viewer filter and sort them.
CSVtoTable deliberately does neither. It takes a static file and produces a static file, and the value of that choice is that the output keeps working when nothing else is available. There is no server to run, no account, no upload of your data anywhere, and no requirement that the person receiving the page can install anything. The cost is that the page is a snapshot. If the underlying data changes after conversion, the page does not know, and the only way to refresh it is to convert again, which for a large file and a non-technical recipient means asking them to run a command.
The other limitation worth stating is scale. The tool does its own work in memory, and while it offers pagination and virtual scrolling to keep large tables responsive in the browser, a very large input still has to be parsed and embedded in one file. For the kind of report a person needs to browse once, that is a good trade. For a dataset meant to be queried repeatedly, it is the wrong shape.
Editorial conclusion
Adopt CSVtoTable when you need to hand someone a spreadsheet view that opens from disk with no server, no network and no installation on their side, and skip it when the audience needs live data, because the page is a snapshot built at conversion time. Before you rely on it, check the browser requirement if you keep compression on, since the self-unpacking script needs DecompressionStream and older browsers get a message instead of a table, and read the released version, since the current tag is a beta and the older 2.x flags still work but disable the features they replaced.
Frequently asked questions
How do I run csvtotable without installing it?
Use uvx csvtotable data.csv data.html for the Python packaging, or npx @vividvilla/csvtotable data.csv data.html for the Node one. Both run a published version without a permanent install.
Does the generated HTML page work offline?
By default the data, styles and scripts are inlined into one file, so the page opens from disk with no network. The script is gzipped and base64 encoded into the page, and unpacking needs DecompressionStream, which older browsers do not have; --no-compress inlines readable source instead.
What is the difference between a single HTML file and the --split output?
The split mode writes a directory instead of one page, with the stylesheet, frontend and row data in separate files whose names carry a content hash, so unchanged files keep their URL and the browser can cache them. Nothing in the page needs fetch, so index.html still opens from disk.
Can csvtotable read Excel files and gzip archives?
Yes. CSV, TSV and .xlsx workbooks are accepted, and gzip compressed CSV files can be passed directly, as in csvtotable data.csv.gz data.html.
Which licence is csvtotable released under?
MIT. The project metadata in the repository lists the licence as MIT, and the package classifiers name the MIT License as an OSI approved licence.
Official sources
Was this useful?