You can build a Dune-style dashboard without a chain indexer or a warehouse
Dune's value isn't the data pipeline — it's the loop where a SQL query becomes a shareable panel. That loop runs on your own CSV, in the browser.
When people say they want "a Dune Analytics alternative," they usually mean one of two completely different things, and the two get conflated constantly. The first is Dune's data: the pre-indexed, decoded on-chain tables — every Ethereum transfer, every Uniswap swap, every contract event, sitting in a warehouse you can query. The second is Dune's workflow: you write SQL, you get a chart, you arrange a few charts into a dashboard, and you ship a link that anyone can open.
Those are not the same product. The first is a multi-million-dollar ETL operation that decodes blockchains. The second is a loop. And the loop is the part most people actually want — because most people asking for a "Dune-style dashboard" aren't querying on-chain data at all. They have a CSV. A query export. A Parquet file someone dropped in Slack. They saw Dune, liked the SQL-to-panel-to-link feel, and want that feel for their data.
This post is about reproducing that exact loop without a warehouse, without a chain indexer, and without a server. If you genuinely need decoded on-chain tables, none of this replaces Dune — I'll be honest about that limit at the end. But if what you're after is the feel of the thing, you can have it on a laptop.
Deconstruct what Dune actually hands you
Strip the data away and Dune is four moves, in order:
- Write SQL against a table. You type a
SELECTwith aGROUP BYand a date truncation, and you get rows back. - Turn the result into a visualization. You point a chart at the query — pick X, pick Y, pick a chart type — and the rows become a line or a bar or a counter.
- Arrange visualizations into a dashboard. A few charts, a couple of big-number tiles, a title, maybe a paragraph of context, stacked into one page.
- Share it as a URL. The dashboard isn't a file you email. It's a link. Whoever opens it sees the live thing.
Notice that only the very first move touches Dune's proprietary data. Moves 2 through 4 are generic. The query result is just rows — it doesn't matter whether those rows came from a decoded blockchain or a CSV you exported from Stripe this morning. The loop that makes Dune feel good is data-agnostic.
So the question becomes: can you reproduce moves 1 through 4 on arbitrary data, in a browser, with no infrastructure? Yes. Here's each piece.
Move 1: real SQL, in the browser, on your CSV
The unlock is DuckDB-WASM — a full analytical SQL engine compiled to WebAssembly that runs in the tab. No server, no connection string. You hand it a CSV (or Parquet) URL, it registers it as a table, and you write real SQL against it: aggregates, joins, window functions, date math. The same query you'd write against a warehouse runs unchanged against a file.
This is the part people don't believe until they see it. You don't upload your data to anyone. The CSV is fetched into the browser, the engine runs locally, and the query executes a few feet from your eyes. Cold-start is a couple of seconds to load the WASM bundle; every query after that is milliseconds.
Here's a query you can paste and adapt — the canonical Dune shape, "volume over time," written against a generic data table that came from a CSV:
SELECT
date_trunc('week', CAST(ts AS TIMESTAMP)) AS week,
SUM(volume_usd) AS volume,
COUNT(*) AS trades
FROM data
WHERE CAST(ts AS TIMESTAMP) >= NOW() - INTERVAL 90 DAY
GROUP BY 1
ORDER BY 1;That's it. date_trunc, SUM, GROUP BY 1 — the exact idiom you'd write in a Dune query editor, running on a file instead of a warehouse. Swap volume_usd for amount, swap week for day, point it at your columns, and you have the spine of any time-series panel.
Move 2 and 3: the query becomes a panel
In Dune you'd click "New visualization," bind the chart to the query, and choose axes through a UI. Plain Sheet does the same binding, but in text. A dashboard in Plain is a Markdown document with fenced panel blocks. One block is a SQL query with an id. The next block is a chart that references that id by name. That's the whole query-to-panel contract — a named query, a chart pointed at the name:
---
plain: sheet@v2
theme: dune-dark
title: Protocol volume
dataSource: https://example.com/trades.csv
---
::: dashboard-header
kicker: WEEKLY · LAST 90 DAYS
title: Volume is recovering off the April floor
description: One CSV, queried in the browser. No warehouse, no indexer.
:::
::: panel sql
title: q1 · weekly volume
id: q_weekly_volume
language: sql
body: |
SELECT
date_trunc('week', CAST(ts AS TIMESTAMP)) AS week,
SUM(volume_usd) AS volume
FROM data
GROUP BY 1
ORDER BY 1;
:::
::: panel line-chart
title: Weekly volume (USD)
subtitle: data = q_weekly_volume
data: q_weekly_volume
yLabel: Volume
yFormat: 0.0a
:::Read the wiring: panel sql declares the query and names it q_weekly_volume. panel line-chart says data: q_weekly_volume — it doesn't re-query, it consumes the result of the named query. That's the same dependency Dune draws between a query and its visualizations, expressed as a one-line reference instead of a UI binding. Add a panel big-number for a headline counter, a panel table with an in-cell bar for a leaderboard, and you've assembled a dashboard — move 3 — by stacking text blocks.
The mental model is worth stating plainly: in Plain, panels are sections in a document, not tiles dragged onto a canvas. You write the page top to bottom and the page is the layout. If that framing is new, the longer argument for it is in building dashboards without a data warehouse.
Move 4: the dashboard is a URL
This is the move people underrate, and it's the one Dune got culturally right. A Dune dashboard isn't a screenshot pasted into a deck. It's a link, and the link is live — whoever opens it runs the queries fresh. That property is what made Dune dashboards spread on crypto Twitter: you could drop one URL and the whole argument traveled with it.
Plain treats this as the default state of every artifact, not just dashboards. The Sheet you generate is a link first. The queries run in the reader's browser when they open it; the CSV is fetched, the SQL executes, the charts hydrate. Nobody downloads an .xlsx and nobody re-runs anything by hand. If you want the full version of why a document being a link beats a document being a file, that's its own piece: the document is a link.
The combined effect is that you've reproduced all four Dune moves — SQL, viz, dashboard, link — and the only thing you supplied was a CSV.
The honest limit: you bring your own data
Here's the trade I promised to be straight about. The thing Dune actually sells — and it is genuinely hard, genuinely valuable — is the data. Decoded, indexed, query-ready on-chain tables. Every block, every log, every token transfer, normalized into something you can SELECT from. That pipeline cost years and a lot of engineers to build, and a browser-local SQL engine does not reproduce it. You can't query ethereum.transactions from a CSV you don't have.
So the line is clean:
- If you need pre-indexed on-chain data — you want to query mainnet history you didn't collect yourself — use Dune. That's its moat and it's a real one.
- If you already have the data as a file — a query export, an analyst's Parquet, a metrics CSV, a snapshot someone pulled — and you want the Dune workflow on it, you don't need a warehouse. The loop runs in the browser.
And the two compose nicely in practice. Run your heavy query in Dune, export the result as a CSV, then drive a Plain Sheet off that export. You get Dune's indexing where it's irreplaceable and the SQL-to-link loop where you actually want to author and share. The boundary isn't a competition; it's a handoff.
Why a file-first loop is sometimes the better fit
Beyond on-chain data, there's a class of analytics where the file-first version is simply nicer to live with. Internal metrics rarely need a warehouse: the data is small, it updates when you update the CSV, and the audience is five people who want one link. Standing up an indexer or a BI seat for that is overkill, and you feel the overkill every time you wait on a refresh or chase a permission.
With a browser-local engine, the dataset travels with the document. There's no service to be down, no connection to expire, no row-level access to configure. The cost is that "live" means "live as of the CSV" — you refresh the data by replacing the file, not by streaming from production. For weekly reviews, protocol snapshots, grant reports, and most things that get screenshotted into a deck anyway, that's the right amount of live.
The shortest path to trying it
If you want to feel the loop, do the smallest possible version:
- Get any CSV with a timestamp column and a numeric column — a Dune export, a Stripe export, anything.
- Point a Sheet's
dataSourceat the CSV (a GitHub raw URL, an S3 link, an R2 bucket — anything fetchable). - Write one
panel sqlwith thedate_trunc … SUM … GROUP BY 1query above, give it anid. - Add one
panel line-chartwithdata:set to that id. - Open the link. The query runs in your browser; the chart draws.
That's the entire Dune loop, minus the warehouse. The query becomes a panel; the panel becomes a page; the page is a URL. The data pipeline was never the part you were going to miss — it was the loop, and the loop is portable.