Query files and cloud data with DuckDB SQL inside notes. Freeze results inline as markdown. Optional MotherDuck for cloud compute.
Bring external data into your Obsidian notes via DuckDB SQL, then freeze the results as a plain markdown table so you and any agent reading the vault see query + result as one document.
Works entirely offline with local DuckDB WASM. Add a MotherDuck token to query your cloud data alongside, picking per code block which connection to use.
Install via Community plugins (see Install), then paste this block into any note:
```duckdb
SELECT
o_orderpriority AS priority,
count(*) AS orders,
round(sum(o_totalprice), 2) AS revenue
FROM read_parquet('https://shell.duckdb.org/data/tpch/0_01/parquet/orders.parquet')
GROUP BY 1
ORDER BY revenue DESC
```
For more demo items, check the demo markdown page.
In reading mode the block becomes a SQL panel with Run / Freeze / Clear freeze buttons. Hit Freeze and the result drops in as a markdown table right under the SQL, bracketed by sentinel comments so the next refresh knows what to replace.

Add a MotherDuck token in Settings to enable motherduck blocks against cloud databases or heavier compute:

Dataview is the go-to plugin for querying the vault itself — your frontmatter, tags, and links across notes. This plugin solves the opposite problem: pulling external data (CSV, Parquet, JSON, Excel, Iceberg, Delta, geospatial files, plus your MotherDuck cloud) into a note via DuckDB SQL, and joining across them when you want.
Output is regular markdown wrapped in sentinel comments — so it diffs cleanly in git, renders in any editor (Neovim, VS Code, mobile previews), and stays readable to agents reading the vault.
SELECT count(*) FROM read_parquet('events.parquet')).```motherduck
SELECT brand, SUM(revenue) FROM sales GROUP BY 1 ORDER BY 2 DESC LIMIT 10
```
<!-- md:cache hash=a3f847b2 conn=cloud ts=2026-04-24T14:22:00Z rows=10 -->
| brand | sum(revenue) |
| ----- | ------------ |
| acme | 42000 |
| ... | ... |
<!-- md:cache-end -->
The sentinel carries a query hash, connection, timestamp, and row count. Refresh and freeze replace the sentinel block below the query in place.
Each block picks its backend via the fence type. Both connections can be configured at once and used side-by-side in the same note.
| Fence | Backend | Needs token | Reaches cloud |
|---|---|---|---|
```duckdb |
@duckdb/duckdb-wasm |
no | no |
```motherduck |
@motherduck/wasm-client |
yes | yes |
Local DuckDB has three sub-modes, set via the Path to local DuckDB file setting:
:memory: (default), ephemeral in-memory database. Reset on every Obsidian restart.notes.duckdb for a persistent database in browser-managed storage (Origin Private File System). Survives Obsidian restart, full read/write, lives outside your vault./Users/you/data.duckdb or C:\Users\you\data.duckdb to query an existing .duckdb file from disk. Read-only: writes succeed inside the worker but don't persist back to the file.One click: Add to Obsidian — opens the plugin page directly in Obsidian. Then hit Install and Enable.
Or manually:
That's it for the standard path. Obsidian's update channel handles new releases automatically.
npm install && npm run build, produces main.js.main.js, manifest.json, and styles.css into <your-vault>/.obsidian/plugins/duckdb-motherduck/.From the command palette:

:memory: (default), an OPFS bare filename, or an absolute file path. See Connections above.data.json (see Security below). Prefer a service account token for scoped, individually revocable access; or a personal access token for quick experimentation.rowCap + 1 rows and discards the rest, so heavy queries (FROM 'huge.csv') don't materialize 40k rows in WASM heap just to throw 39 900 of them away. A truncation notice is appended if more rows existed.80.Pick a cadence in the Refresh dropdown above any SQL block to opt that note in for auto-refresh. The plugin writes a duckdb-motherduck-refresh: daily | weekly property to the note's frontmatter:
---
duckdb-motherduck-refresh: daily
duckdb-motherduck-refresh-last: 2026-05-04T10:30:00Z # plugin-managed
---
While Obsidian is running and the Auto-refresh scheduled notes toggle is on, the plugin sweeps once an hour. Notes whose last - now exceeds their cadence get their frozen tables re-materialized. The active editor is skipped to avoid stomping in-progress edits.
After each sweep finishes, if Reset connections after each scheduled refresh is enabled (default on), the plugin terminates the DuckDB and MotherDuck WASM workers to free memory. The next interactive query pays a ~1–2 s init cost; in exchange, you don't carry materialized result sets between sweeps.
If a note errors three sweeps in a row with every block failing (zero blocks refreshed, errors recorded), the plugin auto-strips its duckdb-motherduck-refresh frontmatter so the hourly sweep stops poking it. Partial failures (some blocks succeed, some error) do not count — a working block keeps the schedule alive. Auto-unschedule events are written to the activity log.
The settings page also has:
duckdb-motherduck-refresh (and the plugin-managed -last timestamp) from every note's frontmatter. Use to bulk-disable auto-refresh after experimenting, or to free up the hourly sweep before running heavy queries.The same code path the Refresh button uses is exposed for agents and external triggers:
app.plugins.getPlugin('duckdb-motherduck').api.refreshFile(path): re-runs every block in the note, returns the refresh count.app.plugins.getPlugin('duckdb-motherduck').api.runQuery(sql, connection?): runs ad-hoc SQL. connection is "local" or "cloud", defaults to "local".Wired into a shell via the official Obsidian CLI:
obsidian eval code="app.plugins.getPlugin('duckdb-motherduck').api.refreshFile('path/to/note.md')"
Drop that into a cron, a Claude Code skill, or any agent that has shell access.
npm install
npm test # automated unit tests for parser/cache/table helpers
npm run build # production bundle, main.js
npm run dev # watch mode, rebuilds on save
main.js ends up around 2 MB because the local DuckDB WASM worker script is bundled inline. The .wasm binary itself is fetched from jsDelivr at runtime (see Remote assets).
The plugin makes network calls only in response to actions you take. There is no telemetry, no analytics, no calls to motherduck.com or any third-party for any reason other than fulfilling a SQL query or fetching a WASM runtime you triggered.
Concretely:
| When | What | Where | Sends |
|---|---|---|---|
First time a duckdb block runs |
DuckDB WASM binary (~7 MB gzipped), once, then browser-cached | https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@<version>/dist/duckdb-eh.wasm |
nothing — public CDN GET |
First time a motherduck block runs |
MotherDuck WASM extension + DuckDB worker bundle | https://app.motherduck.com/duckdb-wasm-assets/<version>/ |
nothing — public assets GET |
Every motherduck block run (including scheduled refresh) |
Your SQL query and your token | Your MotherDuck workspace | your SQL + your token |
| Scheduled refresh sweep (when enabled) | An hourly sweep checks frontmatter-opted-in notes and re-runs their blocks. Only motherduck blocks make network calls; duckdb blocks stay local. The sweep itself does no network I/O — it walks the vault and triggers blocks. |
Same as the row above (only motherduck blocks touch the network) |
Same as above |
Scheduled refresh is off by default and opted into per-note via the Refresh: daily/weekly dropdown above any SQL block, which writes duckdb-motherduck-refresh: daily|weekly to that note's frontmatter. Toggle it off globally in Settings → Auto-refresh scheduled notes.
:memory: and OPFS modes should work; absolute-path mode requires Node integration not available on mobile.The MotherDuck token, if set, is stored plaintext in <vault>/.obsidian/plugins/duckdb-motherduck/data.json. Don't commit that file. Don't sync your vault publicly with a token in it. Keychain integration isn't implemented; this matches the Obsidian-plugin-ecosystem norm (no plugin SDK API for encrypted secrets, no native deps shipped via the community store).
Queries run locally (duckdb blocks) or against your MotherDuck account (motherduck blocks). No telemetry is sent by the plugin.
:memory: and OPFS modes, but hasn't been tested on iOS/Android. Absolute-path mode requires Node integration which isn't available on mobile..duckdb file lets you query it, but writes (CREATE / INSERT / UPDATE) succeed only inside the worker and don't persist back to the file.data.json. See Security.MIT. See LICENSE.