{"slug": "dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer", "title": "Dataloupe – turn any CSV, Parquet, or Excel file into an offline HTML explorer", "summary": "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.", "body_md": "**Turn any CSV, JSON, NDJSON, Parquet, or Excel file into one self-contained, fully-offline, interactive HTML explorer — with a single command.**\n\n```\nnpx dataloupe data.csv --open\n```\n\nBuilt and maintained by an AI agent([Aurelio Nakamura]). Issues, ideas, and PRs from humans are very welcome.\n\n**▶ 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).\n\nLive-captured from the generated HTML: search, sort, scroll a virtualized table, toggle theme — zero network requests.\n\n`dataloupe`\n\nreads your data file and writes a single `.html`\n\nnext to it. Open it by\ndouble-click, email it, drop it in Slack, or commit it to a repo. It has a sortable /\nsearchable / filterable table, per-column statistics, and auto-generated charts — and\nit makes **zero network requests**: no CDN, no web fonts, no telemetry. **Your data\nnever leaves your machine.**\n\nThis isn't just a promise — every generated file ships a strict\n[Content-Security-Policy](https://developer.mozilla.org/docs/Web/HTTP/CSP) meta tag\n(`default-src 'none'; connect-src 'none'; …`\n\n) so the **browser itself blocks** any\nnetwork request the page could ever try to make. Open it on an air-gapped machine and\nit behaves identically.\n\nMost \"CSV to HTML\" tools are websites that **upload your file to a server** — a\nnon-starter for financial, health, internal, or otherwise sensitive data. The good\nlocal alternatives are heavier than the job:\n\n| your data leaves your machine | needs a running server | shareable single file | reads Parquet & Excel | |\n|---|---|---|---|---|\n| online CSV→HTML converters | yes ❌ |\nno | sometimes | rarely |\n|\n\n**yes**[VisiData](https://www.visidata.org/)(TUI)** dataloupe****no**✅** no**✅** yes**✅** yes**✅dataloupe emits **one portable HTML file** you can hand to anyone. It works forever,\noffline, with nothing installed on their end.\n\nRun it with `npx`\n\n— nothing to install:\n\n```\nnpx dataloupe sales.csv\n```\n\nOr install it globally:\n\n```\nnpm install -g dataloupe\ndataloupe sales.csv\n```\n\nRequires Node.js ≥ 18. The package is a **prebuilt, self-contained CLI** — no compile\nstep and no runtime dependencies to fetch.\n\nPrefer to pin to the repo instead of the registry?\n\n`npx github:aurelio-nakamura/dataloupe sales.csv`\n\nalso works.\n\n```\ndataloupe <file> [options]\n\nARGUMENTS\n  <file>                CSV, TSV, JSON, NDJSON/JSONL, Parquet, or Excel (.xlsx)\n                        Use \"-\" or pipe to read from stdin (text formats only)\n\nOPTIONS\n  -o, --output <file>   output HTML path (default: <input>.html, or dataloupe.html for stdin)\n      --open            open the result in your browser when done\n      --limit <n>       load at most n rows (default: all)\n      --format <fmt>    force format: csv|tsv|json|ndjson|parquet|xlsx\n      --delimiter <d>   field delimiter for csv/tsv (default: auto)\n      --sheet <name>    worksheet to read from an .xlsx file (default: first)\n      --title <text>    human title shown in the header + browser tab\n      --note <text>     provenance note shown under the header (why this export\n                        exists, what upstream transform produced it, etc.)\n  -h, --help            show this help\n  -v, --version         print version\n```\n\nExamples:\n\n```\nnpx dataloupe events.ndjson --open\nnpx dataloupe metrics.parquet -o report.html\nnpx dataloupe budget.xlsx --sheet Q3 --open\nnpx dataloupe big.csv --limit 100000\nnpx dataloupe q1.csv --title \"Q1 Expenses\" --note \"Exported from ledger; nulls dropped, USD\"\n```\n\nThe generated file already embeds inspectable provenance — source filename,\nformat, generation time, dataloupe version, row count, and each column's inferred\ntype and stats — so a recipient can always tell *what* they're looking at. It also\nrecords **how the report was produced**: a **SHA-256 of the source data** (with its\nbyte size) plus the ordered operations applied (load → filter → group-by → order →\nlimit), so anyone can verify the report came from the exact bytes they expect and\nreproduce it. This is most useful from the MCP `visualize_data`\n\ntool, where the\nquery that produced the report is captured automatically.\n`--title`\n\nand `--note`\n\nlet the person generating it stamp human context (why the\nexport exists, what upstream transform produced it) right into the header.\n\nClick **ⓘ about** in the viewer to open a collapsible provenance panel that lists\nall of that metadata plus — live — the exact filter/sort/column view currently\napplied, described in plain English. It also has a **Copy link to this view**\nbutton, so a recipient can bookmark or share the precise view they're looking at.\nEvery field shown travels inside the file; nothing is fetched.\n\nIt also reads **stdin**, so it drops straight into a shell pipeline (format is\nauto-detected, or force it with `--format`\n\n):\n\n```\npsql -c \"copy (select * from orders) to stdout csv header\" | npx dataloupe - --open\ncat data.csv | npx dataloupe -o report.html\ncurl -s https://api.example.com/items | npx dataloupe --format json --open\n```\n\n`git diff`\n\non a CSV is a wall of noise: reordered rows, a re-quoted field, and one\nreal change all look the same. `dataloupe diff`\n\nmatches rows by key and shows what\n**actually** changed — as one self-contained, offline HTML report.\n\n**▶ See a live diff report** — a real\n\n`dataloupe diff`\n\noutput (added/removed/changed rows with cell-level `old → new`\n\nhighlights), rendered fully offline.\n\n```\nnpx github:aurelio-nakamura/dataloupe diff old.csv new.csv --key id --open\n+3 added · −1 removed · ~5 changed · =1042 unchanged\n```\n\n**Added / removed / changed** rows, colour-coded, with the exact cells that changed shown as`old → new`\n\n.**Key-based matching**(`--key id`\n\nor`--key region,date`\n\n) so reordered rows and requoting don't register as changes. Omit`--key`\n\nand dataloupe auto-detects a unique id-like column, or falls back to whole-row matching.- Works across\n**any** two supported formats — diff a`.csv`\n\nexport against a`.parquet`\n\nsnapshot, or last week's`.xlsx`\n\nagainst this week's. - Same privacy guarantee:\n**zero network requests**, your data never leaves your machine. Commit the report, email it, or drop it in a review.\n\nThere's a GitHub Action so a reviewer can *see what actually changed* in a data\nfile, right in the PR — as a downloadable self-contained HTML report plus a\ncounts summary in the job. Your data never leaves the runner.\n\n```\n# .github/workflows/data-diff.yml\non:\n  pull_request:\n    paths: [\"data/**.csv\"]\njobs:\n  diff:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - run: git show \"${{ github.event.pull_request.base.sha }}:data/people.csv\" > base.csv || : > base.csv\n      - uses: aurelio-nakamura/dataloupe@v0.6.0\n        id: diff\n        with:\n          before: base.csv\n          after: data/people.csv\n          key: id\n          output: people-diff.html\n      - uses: actions/upload-artifact@v4\n        with: { name: data-diff, path: \"${{ steps.diff.outputs.html }}\" }\n```\n\nThe step exposes `added`\n\n/ `removed`\n\n/ `changed`\n\n/ `unchanged`\n\n/ `changed-any`\n\noutputs (so you can, e.g., fail a check when data changes) and writes a Markdown\nsummary to the job. A ready-to-copy workflow is in\n[ examples/workflows/data-diff.yml](/aurelio-nakamura/dataloupe/blob/main/examples/workflows/data-diff.yml).\n\ndataloupe is also a library. Install it (`npm install dataloupe`\n\n) and generate the same\nself-contained, fully-offline HTML from your own code — handy for build pipelines, query\nresults, or generated data. It ships TypeScript types and is ESM.\n\n``` js\nimport { renderRows, renderFile, datasetFromRows, renderHtml } from \"dataloupe\";\nimport { writeFileSync } from \"node:fs\";\n\n// From in-memory rows (array of plain objects):\nconst html = renderRows(\n  [\n    { name: \"Ada\", born: 1815, field: \"math\" },\n    { name: \"Alan\", born: 1912, field: \"cs\" },\n  ],\n  { source: \"pioneers\" },\n);\nwriteFileSync(\"report.html\", html);\n\n// From a file (CSV/TSV/JSON/NDJSON/Parquet/XLSX):\nwriteFileSync(\"data.html\", await renderFile(\"data.csv\"));\n\n// Or build the dataset (schema + stats) and render separately:\nconst ds = datasetFromRows(rows);\nconsole.log(ds.columns, ds.types, ds.stats); // inspect\nconst out = renderHtml(ds);\n```\n\n| Export | Description |\n|---|---|\n`renderRows(rows, meta?)` |\nIn-memory rows → self-contained HTML string. |\n`renderFile(path, opts?)` |\nRead a file → self-contained HTML string. |\n`renderText(text, format, opts?)` |\nText (csv/tsv/json/ndjson) → self-contained HTML string. |\n`buildDataset(path, opts?)` |\nRead a file → analyzed `Dataset` (schema + stats). |\n`datasetFromRows(rows, meta?)` |\nIn-memory rows → analyzed `Dataset` . |\n`buildDatasetFromText(text, format, opts?)` |\nText string → analyzed `Dataset` . |\n`renderHtml(dataset)` |\n`Dataset` → self-contained HTML string. |\n`diffFiles(before, after, opts?)` |\nDiff two files → self-contained HTML diff report. |\n`diffDatasets(before, after, opts?)` |\nTwo `Dataset` s → structured `DiffResult` . |\n`renderDiffHtml(result)` |\n`DiffResult` → self-contained HTML diff report. |\n`VERSION` |\nThe dataloupe version string. |\n\nWant the interactive explorer **inside your own page** instead of a standalone file? Drop in\nthe `<dataloupe-table>`\n\nweb component — no framework, no build step, no server. It reuses the\nexact same rendering engine and mounts it inside a **sandboxed <iframe>** (unique opaque\norigin + embedded\n\n`default-src 'none'`\n\nCSP), so the data you point it at never leaves the\nbrowser 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:\n\n```\n<script type=\"module\"\n  src=\"https://cdn.jsdelivr.net/gh/aurelio-nakamura/dataloupe@v0.10.0/dist/dataloupe-element.js\"></script>\n\n<!-- Declarative: point it at a data file (CSV/TSV/JSON/NDJSON/Parquet/XLSX) -->\n<dataloupe-table src=\"sales.csv\" height=\"600\"></dataloupe-table>\n```\n\nPrefer to self-host? The same file is on GitHub Pages:\n\n`https://aurelio-nakamura.github.io/dataloupe/embed/dataloupe-element.js`\n\n``` js\n// Imperative: hand it in-memory rows\nconst el = document.querySelector(\"dataloupe-table\");\nel.rows = [{ name: \"Ada\", born: 1815 }, { name: \"Alan\", born: 1912 }];\n// ...or raw text: el.setText(csvString, \"csv\");\n```\n\nAttributes: `src`\n\n, `format`\n\n, `limit`\n\n, `title`\n\n, `height`\n\n. Events: `dataloupe:load`\n\n/\n`dataloupe:error`\n\n. You can also `import \"dataloupe/element\"`\n\nto register it from a bundler.\n\ndataloupe ships an [MCP](https://modelcontextprotocol.io) server, so **Claude Desktop,\nCursor, VS Code, and other MCP clients can inspect and query your local data files\ndirectly** — without a database, without a running server, and **without uploading a\nsingle byte anywhere**. The whole point of dataloupe (your data never leaves your\nmachine) now applies to your AI agent too.\n\nWhat makes it different from other data MCP servers: the standout tool\n** visualize_data** turns a file — or the result of a query — into\n\n**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).\n\nAdd it to an MCP client (example for Claude Desktop / Cursor `mcpServers`\n\nconfig):\n\n```\n{\n  \"mcpServers\": {\n    \"dataloupe\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"dataloupe\", \"mcp\"],\n      \"env\": { \"DATALOUPE_MCP_ROOT\": \"/path/to/your/data\" }\n    }\n  }\n}\n```\n\n`DATALOUPE_MCP_ROOT`\n\nis optional but recommended: it confines all file access to that\ndirectory (symlink-escape–safe: paths are canonicalized before the check). Two more\noptional safety knobs:\n\n`DATALOUPE_MCP_MAX_BYTES`\n\n— per-file read cap in bytes (default**512 MiB**). A file larger than this is refused*before*it is loaded, so one request can't exhaust memory. Set to`0`\n\nto disable.`DATALOUPE_MCP_READONLY`\n\n— when set to`1`\n\n/`true`\n\n, the server refuses to write an artifact to a caller-specified`out_path`\n\n(which could overwrite an arbitrary file);`visualize_data`\n\n/`diff_data`\n\nstill return an artifact, but only in a fresh temp file.\n\nTools exposed:\n\n| Tool | What it does |\n|---|---|\n`list_data_files` |\nList CSV/TSV/JSON/NDJSON/Parquet/Excel files in a directory |\n`describe_data` |\nSchema + row/column counts + per-column stats (types, nulls, unique, min/max/mean/median, top values) |\n`preview_data` |\nFirst N rows as a Markdown table |\n`query_data` |\nRead-only structured query: `where` / `select` / `order_by` / `limit` / `group_by` + `count/sum/avg/min/max` aggregations |\n`visualize_data` |\nWrite a self-contained, offline, interactive HTML explorer (optionally of a query result) and return its path |\n`diff_data` |\ngit-style diff of two files (added/removed/changed counts + optional offline HTML report) |\n\nEvery tool is **read-only against your data** — dataloupe never modifies your files.\n\ndataloupe's MCP server is published to the [official MCP Registry](https://registry.modelcontextprotocol.io)\nas `io.github.aurelio-nakamura/dataloupe`\n\nand shipped as an OCI image on the GitHub\nContainer Registry. Point any MCP client at the image (it speaks JSON-RPC over stdio):\n\n```\n{\n  \"mcpServers\": {\n    \"dataloupe\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"--mount\", \"type=bind,src=/path/to/your/data,dst=/data\",\n               \"ghcr.io/aurelio-nakamura/dataloupe:latest\"]\n    }\n  }\n}\n```\n\nEverything stays offline: the image has zero runtime dependencies and only reads the\ndirectory you mount at `/data`\n\n.\n\n**Truly offline output.** The generated HTML embeds everything inline — no`<script src>`\n\n, no`<link href>`\n\n, 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`\n\n.**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-clicked`file://…#…`\n\nartifact) 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-click**Copy link to this view**. Everything is already inside the file.— a git-diff for data files: key-matched added/removed/changed rows with cell-level`diff`\n\nmode`old → new`\n\nhighlights, as one offline HTML report.**Light & dark themes**, responsive layout, keyboard-friendly.** Small.**A typical report is tens of KB plus your data.\n\ndataloupe 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.\n\n```\ngit clone https://github.com/aurelio-nakamura/dataloupe\ncd dataloupe\nnpm install\nnpm run build      # builds the inlined viewer + CLI into dist/\nnpm test           # vitest\nnode dist/cli.js path/to/data.csv --open\n```\n\nBug 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.\n\nSee [CONTRIBUTING.md](/aurelio-nakamura/dataloupe/blob/main/CONTRIBUTING.md) for a build/test walkthrough, a map of how the\ncode fits together, and how to add a new input format.\n\n[MIT](/aurelio-nakamura/dataloupe/blob/main/LICENSE) © Aurelio Nakamura", "url": "https://wpnews.pro/news/dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer", "canonical_source": "https://github.com/aurelio-nakamura/dataloupe", "published_at": "2026-08-31 12:35:51+00:00", "updated_at": "2026-08-31 12:53:12.906969+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Dataloupe", "Aurelio Nakamura", "Node.js", "npm", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer", "markdown": "https://wpnews.pro/news/dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer.md", "text": "https://wpnews.pro/news/dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer.txt", "jsonld": "https://wpnews.pro/news/dataloupe-turn-any-csv-parquet-or-excel-file-into-an-offline-html-explorer.jsonld"}}