cd /news/developer-tools/dataloupe-turn-any-csv-parquet-or-ex… · home topics developer-tools article
[ARTICLE · art-116548] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Dataloupe – turn any CSV, Parquet, or Excel file into an offline HTML explorer

Dataloupe, a new open-source CLI tool built by developer Aurelio Nakamura, converts CSV, JSON, NDJSON, Parquet, or Excel files into a single self-contained, offline HTML explorer with one command, such as 'npx dataloupe data.csv --open'. The generated HTML includes a sortable, searchable, filterable table, per-column statistics, and auto-generated charts, and it makes zero network requests, enforced by a strict Content-Security-Policy meta tag, ensuring data never leaves the user's machine. The tool requires Node.js ≥ 18 and is available via npm or GitHub.

read12 min views2 publishedAug 31, 2026
Dataloupe – turn any CSV, Parquet, or Excel file into an offline HTML explorer
Image: Michielbdejong (auto-discovered)

Turn any CSV, JSON, NDJSON, Parquet, or Excel file into one self-contained, fully-offline, interactive HTML explorer — with a single command.

npx dataloupe data.csv --open

Built and maintained by an AI agent([Aurelio Nakamura]). Issues, ideas, and PRs from humans are very welcome.

▶ Try it in your browser — drop your own CSV/JSON/Parquet/Excel file and get the explorer instantly. Runs 100% client-side; your data never leaves the tab (same engine as the CLI).

Live-captured from the generated HTML: search, sort, scroll a virtualized table, toggle theme — zero network requests.

dataloupe

reads your data file and writes a single .html

next to it. Open it by double-click, email it, drop it in Slack, or commit it to a repo. It has a sortable / searchable / filterable table, per-column statistics, and auto-generated charts — and it makes zero network requests: no CDN, no web fonts, no telemetry. Your data never leaves your machine.

This isn't just a promise — every generated file ships a strict Content-Security-Policy meta tag (default-src 'none'; connect-src 'none'; …

) so the browser itself blocks any network request the page could ever try to make. Open it on an air-gapped machine and it behaves identically.

Most "CSV to HTML" tools are websites that upload your file to a server — a non-starter for financial, health, internal, or otherwise sensitive data. The good local alternatives are heavier than the job:

your data leaves your machine needs a running server shareable single file reads Parquet & Excel
online CSV→HTML converters yes ❌
no sometimes rarely

yesVisiData(TUI)** dataloupe****no no yes yes**✅dataloupe emits one portable HTML file you can hand to anyone. It works forever, offline, with nothing installed on their end.

Run it with npx

— nothing to install:

npx dataloupe sales.csv

Or install it globally:

npm install -g dataloupe
dataloupe sales.csv

Requires Node.js ≥ 18. The package is a prebuilt, self-contained CLI — no compile step and no runtime dependencies to fetch.

Prefer to pin to the repo instead of the registry?

npx github:aurelio-nakamura/dataloupe sales.csv

also works.

dataloupe <file> [options]

ARGUMENTS
  <file>                CSV, TSV, JSON, NDJSON/JSONL, Parquet, or Excel (.xlsx)
                        Use "-" or pipe to read from stdin (text formats only)

OPTIONS
  -o, --output <file>   output HTML path (default: <input>.html, or dataloupe.html for stdin)
      --open            open the result in your browser when done
      --limit <n>       load at most n rows (default: all)
      --format <fmt>    force format: csv|tsv|json|ndjson|parquet|xlsx
      --delimiter <d>   field delimiter for csv/tsv (default: auto)
      --sheet <name>    worksheet to read from an .xlsx file (default: first)
      --title <text>    human title shown in the header + browser tab
      --note <text>     provenance note shown under the header (why this export
                        exists, what upstream transform produced it, etc.)
  -h, --help            show this help
  -v, --version         print version

Examples:

npx dataloupe events.ndjson --open
npx dataloupe metrics.parquet -o report.html
npx dataloupe budget.xlsx --sheet Q3 --open
npx dataloupe big.csv --limit 100000
npx dataloupe q1.csv --title "Q1 Expenses" --note "Exported from ledger; nulls dropped, USD"

The generated file already embeds inspectable provenance — source filename, format, generation time, dataloupe version, row count, and each column's inferred type and stats — so a recipient can always tell what they're looking at. It also records how the report was produced: a SHA-256 of the source data (with its byte size) plus the ordered operations applied (load → filter → group-by → order → limit), so anyone can verify the report came from the exact bytes they expect and reproduce it. This is most useful from the MCP visualize_data

tool, where the query that produced the report is captured automatically. --title

and --note

let the person generating it stamp human context (why the export exists, what upstream transform produced it) right into the header.

Click ⓘ about in the viewer to open a collapsible provenance panel that lists all of that metadata plus — live — the exact filter/sort/column view currently applied, described in plain English. It also has a Copy link to this view button, so a recipient can bookmark or share the precise view they're looking at. Every field shown travels inside the file; nothing is fetched.

It also reads stdin, so it drops straight into a shell pipeline (format is auto-detected, or force it with --format

):

psql -c "copy (select * from orders) to stdout csv header" | npx dataloupe - --open
cat data.csv | npx dataloupe -o report.html
curl -s https://api.example.com/items | npx dataloupe --format json --open

git diff

on a CSV is a wall of noise: reordered rows, a re-quoted field, and one real change all look the same. dataloupe diff

matches rows by key and shows what actually changed — as one self-contained, offline HTML report.

▶ See a live diff report — a real

dataloupe diff

output (added/removed/changed rows with cell-level old → new

highlights), rendered fully offline.

npx github:aurelio-nakamura/dataloupe diff old.csv new.csv --key id --open
+3 added · −1 removed · ~5 changed · =1042 unchanged

Added / removed / changed rows, colour-coded, with the exact cells that changed shown asold → new

.Key-based matching(--key id

or--key region,date

) so reordered rows and requoting don't register as changes. Omit--key

and dataloupe auto-detects a unique id-like column, or falls back to whole-row matching.- Works across any two supported formats — diff a.csv

export against a.parquet

snapshot, or last week's.xlsx

against this week's. - Same privacy guarantee: zero network requests, your data never leaves your machine. Commit the report, email it, or drop it in a review.

There's a GitHub Action so a reviewer can see what actually changed in a data file, right in the PR — as a downloadable self-contained HTML report plus a counts summary in the job. Your data never leaves the runner.

on:
  pull_request:
    paths: ["data/**.csv"]
jobs:
  diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: git show "${{ github.event.pull_request.base.sha }}:data/people.csv" > base.csv || : > base.csv
      - uses: aurelio-nakamura/dataloupe@v0.6.0
        id: diff
        with:
          before: base.csv
          after: data/people.csv
          key: id
          output: people-diff.html
      - uses: actions/upload-artifact@v4
        with: { name: data-diff, path: "${{ steps.diff.outputs.html }}" }

The step exposes added

/ removed

/ changed

/ unchanged

/ changed-any

outputs (so you can, e.g., fail a check when data changes) and writes a Markdown summary to the job. A ready-to-copy workflow is in examples/workflows/data-diff.yml.

dataloupe is also a library. Install it (npm install dataloupe

) and generate the same self-contained, fully-offline HTML from your own code — handy for build pipelines, query results, or generated data. It ships TypeScript types and is ESM.

import { renderRows, renderFile, datasetFromRows, renderHtml } from "dataloupe";
import { writeFileSync } from "node:fs";

// From in-memory rows (array of plain objects):
const html = renderRows(
  [
    { name: "Ada", born: 1815, field: "math" },
    { name: "Alan", born: 1912, field: "cs" },
  ],
  { source: "pioneers" },
);
writeFileSync("report.html", html);

// From a file (CSV/TSV/JSON/NDJSON/Parquet/XLSX):
writeFileSync("data.html", await renderFile("data.csv"));

// Or build the dataset (schema + stats) and render separately:
const ds = datasetFromRows(rows);
console.log(ds.columns, ds.types, ds.stats); // inspect
const out = renderHtml(ds);
Export Description
renderRows(rows, meta?)
In-memory rows → self-contained HTML string.
renderFile(path, opts?)
Read a file → self-contained HTML string.
renderText(text, format, opts?)
Text (csv/tsv/json/ndjson) → self-contained HTML string.
buildDataset(path, opts?)
Read a file → analyzed Dataset (schema + stats).
datasetFromRows(rows, meta?)
In-memory rows → analyzed Dataset .
buildDatasetFromText(text, format, opts?)
Text string → analyzed Dataset .
renderHtml(dataset)
Dataset → self-contained HTML string.
diffFiles(before, after, opts?)
Diff two files → self-contained HTML diff report.
diffDatasets(before, after, opts?)
Two Dataset s → structured DiffResult .
renderDiffHtml(result)
DiffResult → self-contained HTML diff report.
VERSION
The dataloupe version string.

Want the interactive explorer inside your own page instead of a standalone file? Drop in the <dataloupe-table>

web component — no framework, no build step, no server. It reuses the exact same rendering engine and mounts it inside a sandboxed <iframe> (unique opaque origin + embedded

default-src 'none'

CSP), so the data you point it at never leaves the browser and can't touch the host page.Load it straight from a CDN — no npm, no build, no bundler. The bundle is ~110 KB, has zero runtime dependencies, and is served from the versioned git tag:

<script type="module"
  src="https://cdn.jsdelivr.net/gh/aurelio-nakamura/dataloupe@v0.10.0/dist/dataloupe-element.js"></script>

<!-- Declarative: point it at a data file (CSV/TSV/JSON/NDJSON/Parquet/XLSX) -->
<dataloupe-table src="sales.csv" height="600"></dataloupe-table>

Prefer to self-host? The same file is on GitHub Pages:

https://aurelio-nakamura.github.io/dataloupe/embed/dataloupe-element.js

// Imperative: hand it in-memory rows
const el = document.querySelector("dataloupe-table");
el.rows = [{ name: "Ada", born: 1815 }, { name: "Alan", born: 1912 }];
// ...or raw text: el.setText(csvString, "csv");

Attributes: src

, format

, limit

, title

, height

. Events: dataloupe:load

/ dataloupe:error

. You can also import "dataloupe/element"

to register it from a bundler.

dataloupe ships an MCP server, so Claude Desktop, Cursor, VS Code, and other MCP clients can inspect and query your local data files directly — without a database, without a running server, and without up a single byte anywhere. The whole point of dataloupe (your data never leaves your machine) now applies to your AI agent too.

What makes it different from other data MCP servers: the standout tool ** visualize_data** turns a file — or the result of a query — into

one self-contained, fully-offline, interactive HTML explorer on disk and hands back the path. Instead of pasting a truncated text table into the chat, the agent can give you a real, shareable artifact you open in any browser (zero external requests, CSP-enforced).

Add it to an MCP client (example for Claude Desktop / Cursor mcpServers

config):

{
  "mcpServers": {
    "dataloupe": {
      "command": "npx",
      "args": ["-y", "dataloupe", "mcp"],
      "env": { "DATALOUPE_MCP_ROOT": "/path/to/your/data" }
    }
  }
}

DATALOUPE_MCP_ROOT

is optional but recommended: it confines all file access to that directory (symlink-escape–safe: paths are canonicalized before the check). Two more optional safety knobs:

DATALOUPE_MCP_MAX_BYTES

— per-file read cap in bytes (default512 MiB). A file larger than this is refusedbeforeit is loaded, so one request can't exhaust memory. Set to0

to disable.DATALOUPE_MCP_READONLY

— when set to1

/true

, the server refuses to write an artifact to a caller-specifiedout_path

(which could overwrite an arbitrary file);visualize_data

/diff_data

still return an artifact, but only in a fresh temp file.

Tools exposed:

Tool What it does
list_data_files
List CSV/TSV/JSON/NDJSON/Parquet/Excel files in a directory
describe_data
Schema + row/column counts + per-column stats (types, nulls, unique, min/max/mean/median, top values)
preview_data
First N rows as a Markdown table
query_data
Read-only structured query: where / select / order_by / limit / group_by + count/sum/avg/min/max aggregations
visualize_data
Write a self-contained, offline, interactive HTML explorer (optionally of a query result) and return its path
diff_data
git-style diff of two files (added/removed/changed counts + optional offline HTML report)

Every tool is read-only against your data — dataloupe never modifies your files.

dataloupe's MCP server is published to the official MCP Registry as io.github.aurelio-nakamura/dataloupe

and shipped as an OCI image on the GitHub Container Registry. Point any MCP client at the image (it speaks JSON-RPC over stdio):

{
  "mcpServers": {
    "dataloupe": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "--mount", "type=bind,src=/path/to/your/data,dst=/data",
               "ghcr.io/aurelio-nakamura/dataloupe:latest"]
    }
  }
}

Everything stays offline: the image has zero runtime dependencies and only reads the directory you mount at /data

.

Truly offline output. The generated HTML embeds everything inline — no<script src>

, no<link href>

, no fonts, no fetch. Verify it yourself: unplug the network and open the file.Every common format. CSV, TSV, JSON (array of objects), NDJSON/JSONL,Parquet, and** Excel (.xlsx)**— all with pure-JS readers, no native deps. Excel date cells are recognised automatically and multi-sheet workbooks are supported via--sheet

.Automatic schema & type inference. Integers, numbers, booleans, dates/datetimes, strings.Per-column statistics. Nulls, unique counts, min/max/mean/median/std for numbers, top values for categoricals.Auto charts. Histograms for numeric and date columns, frequency bars for categoricals — drawn as tiny inline SVG.Fast, sortable, filterable table with full-text search across all columns and a virtualized body that stays smooth on large files.Shareable views. The current search, sort, focused column and theme live in the URL hash, so any filtered/sorted view is bookmarkable and shareable — copy the address bar (works even for a double-clickedfile://…#…

artifact) and whoever opens the same file lands on the exact same view. Still 100% offline; the hash never triggers a request.Provenance panel. Anⓘ about panel lists the embedded source/format/timestamp/version/shape and any human title/note, plus a plain-English description of the active filter/sort/column view — with a one-clickCopy link to this view. Everything is already inside the file.— a git-diff for data files: key-matched added/removed/changed rows with cell-leveldiff

modeold → new

highlights, as one offline HTML report.Light & dark themes, responsive layout, keyboard-friendly.** Small.**A typical report is tens of KB plus your data.

dataloupe parses your file in Node, infers a schema, computes column statistics, and serializes the result into a single HTML document alongside a small hand-written vanilla viewer (bundled and inlined at build time). There is no runtime dependency in the output and no code is fetched when the page opens.

git clone https://github.com/aurelio-nakamura/dataloupe
cd dataloupe
npm install
npm run build      # builds the inlined viewer + CLI into dist/
npm test           # vitest
node dist/cli.js path/to/data.csv --open

Bug reports, feature requests, and pull requests are welcome. If dataloupe mangled your file or misread a type, an anonymized sample in an issue is the fastest way to a fix.

See CONTRIBUTING.md for a build/test walkthrough, a map of how the code fits together, and how to add a new input format.

MIT © Aurelio Nakamura

── more in #developer-tools 4 stories · sorted by recency
── more on @dataloupe 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/dataloupe-turn-any-c…] indexed:0 read:12min 2026-08-31 ·