SQLite to Markdown
approvedby Michael Collard
Render SQLite queries as markdown tables via fenced code blocks. - This plugin has not been manually reviewed by Obsidian staff.
SQLite to Markdown
An Obsidian plugin that runs SQLite queries against local databases and renders the results as tables in reading view. Write SQL in a fenced code block, get a live table.
Usage
Add a fenced sql (or sqlite) code block to any note:
```sql
db: /var/lib/jellyfin/data/jellyfin.db
SELECT name, department, salary
FROM employees
WHERE department = 'Engineering'
ORDER BY salary DESC;
```
Or use an ODBC-style connection string with an ObsSync block:
```ObsSync
DRIVER=sqlite;DATABASE=/var/lib/jellyfin/data/jellyfin.db;QUERY=SELECT name, department, salary FROM employees WHERE department = 'Engineering' ORDER BY salary DESC
```
Switch to reading view โ the code block is replaced with a rendered table:
| name | department | salary |
|---|---|---|
| Eve Johnson | Engineering | 140000 |
| Carol Davis | Engineering | 130000 |
| Alice Chen | Engineering | 125000 |
| Hank Brown | Engineering | 115000 |
A footer row shows the database filename, when the data was retrieved, and the row count. Click ๐ or ๐ in the footer to manually refresh.
Block Formats
sql / sqlite
db: /absolute/path/to/database.db
SELECT ...;
db:โ path to the SQLite database file. Absolute paths or vault-relative paths.- Query โ any read-only SQL:
SELECT,WITH ... SELECT, orEXPLAIN QUERY PLAN. - Write operations (
INSERT,UPDATE,DELETE,DROP, etc.) are blocked.
ObsSync (ODBC-style)
DRIVER=sqlite;DATABASE=/absolute/path/to/database.db;QUERY=SELECT ...
DRIVER=โ optional, defaults tosqlite. Currently onlysqliteis supported.DATABASE=โ path to the SQLite database file. Absolute paths or vault-relative paths.QUERY=โ must be last; its value extends to end of the block, so semicolons inside the SQL are safe.
Features
- Live query โ reads the database from disk each time the note is rendered (desktop).
- Cached fallback โ after a successful query, results are cached inside the code block. If the database is unavailable, the cached result is shown with a โ ๏ธ cached indicator.
- Mobile support โ on mobile, cached data is displayed read-only. Open on desktop first to populate the cache.
- Refresh modes โ control when queries re-run via the
REFRESH:directive (see below). - Timed auto-refresh โ
REFRESH:15mre-runs the query every N time units while the note is open. - Interactive footer โ ๐ and ๐ in the footer row are clickable; they trigger a live refresh on desktop or show a notice on mobile.
- Copy as Markdown โ button below each table copies the result as a pipe-delimited markdown table.
- Index support โ uses sql.js (SQLite compiled to WebAssembly), so all SQLite features work including indexes, joins, CTEs, and window functions.
- Read-only by design โ the database is loaded into memory as a read-only snapshot. No changes are ever written back.
- Instructional messages โ clear guidance instead of cryptic errors when something is misconfigured.
Refresh Modes
Add a /* REFRESH:... */ comment anywhere in the block to control when the query runs:
| Value | Behaviour |
|---|---|
| (omitted) | Auto โ query runs on every render (default) |
REFRESH:AUTO | Same as default |
REFRESH:MANUAL | Show cache on render; live query only on ๐ click |
REFRESH:15m | Auto on render + re-query every 15 minutes |
Time units: ms ยท s / sec(s) / second(s) ยท m / min(s) / minute(s) ยท h / hr(s) / hour(s) ยท d / day(s). Decimal values (1.5h) are valid.
Examples:
```sql
db: /var/lib/jellyfin/data/jellyfin.db
SELECT Type, count(*) FROM BaseItems GROUP BY Type;
/* REFRESH:5m */
```
```sql
db: /path/to/large.db
SELECT * FROM expensive_view;
/* REFRESH:MANUAL */
```
How Caching Works
After a successful query, the plugin writes a /* REFRESH:... | CACHE:{...} */ comment to the end of the code block. This is invisible in reading view but preserved in the markdown source:
```sql
db: /var/lib/jellyfin/data/jellyfin.db
SELECT Type, count(*) FROM BaseItems GROUP BY Type;
/* REFRESH:5m | CACHE:{"columns":["Type","count(*)"],"rows":[...],"dbName":"jellyfin.db","timestamp":"2026-06-08 14:00:00","rowCount":7} */
```
The cache is updated on each successful query. When the database is unreachable, the last cached result is displayed. If no REFRESH: directive is present, the comment is just /* CACHE:{...} */ (backward compatible).
Installation
From source
npm install
npm run build
Copy main.js, manifest.json, and styles.css into your vault:
.obsidian/plugins/obs-sqlite-md/
โโโ main.js
โโโ manifest.json
โโโ styles.css
Enable the plugin in Settings โ Community plugins.
Development
npm run dev # watch mode โ rebuilds on file changes
Requirements
- Desktop โ live queries use Node.js
fsto read database files from disk. Absolute paths and vault-relative paths both work. - Mobile โ displays cached data only. Open the note on desktop first to populate the cache; subsequent mobile views show the last cached result.
- Databases can live anywhere the desktop OS user has read access (inside or outside the vault).
Technical Notes
- Uses sql.js v1.14+ โ SQLite compiled to WebAssembly via Emscripten.
- The WASM binary (~644 KB) is embedded into
main.jsat build time. No separate.wasmfile to manage. - Databases are read into memory as a
Uint8Array. Large databases (hundreds of MB) will use proportional memory. - Registers
sql,sqlite, andObsSyncas code block languages. - Node.js
fsis required dynamically at runtime (never at module load), so the plugin loads safely on mobile without crashing. - Timed refresh uses a
window.setTimeoutchain; the chain stops automatically whenel.isConnectedis false (note closed or navigated away). - See
ARCHITECTURE.mdfor full design details.
License
MIT
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.