chengmiaofan20 downloadsQuery SQLite databases that live outside your vault and read the results in a side panel. Read-only by design, with cross-database joins, an optional data dictionary, and plain Markdown exports.
Query SQLite databases that live outside your vault with SQL, and read the results in a side panel — without creating a single note.
Most database plugins ask you to store data inside the vault, which does not scale: tens of
thousands of rows become tens of thousands of files, and your vault index and sync pay for it.
Super Useful SQL is the other half of that workflow: keep the bulk data in ordinary .db files
anywhere on disk, and bring only the query and its result into Obsidian.

other.table
works in the same statement.node:sqlite, which is not available on mobile.node:sqlite module comes from Electron, and Electron comes from the installer — not from the
app version. If the plugin reports that node:sqlite is unavailable, download the current installer
from obsidian.md and reinstall (app auto-updates do not refresh
Electron).Manual: copy main.js, manifest.json and styles.css into
<your-vault>/.obsidian/plugins/super-useful-sql/, then enable Super Useful SQL in
Settings → Community plugins.
BRAT: add this repository's URL in the BRAT plugin and install Super Useful SQL.
Create a SQLite file somewhere outside your vault, or use one you already have.
Settings → Super Useful SQL → Add database: set an alias (sales) and the absolute path
(/home/you/data/sales.db).
Open the panel (ribbon database icon, or the command Open query panel).
Write SQL and press Run (or Cmd/Ctrl+Enter):
SELECT region, COUNT(*) AS orders, SUM(total) AS revenue
FROM orders
WHERE created >= '2026-01-01'
GROUP BY region
ORDER BY revenue DESC
LIMIT 20;
Cross-database query (main database = sales, other registered database attached as crm):
SELECT s.region, COUNT(*) AS n, c.owner
FROM orders s
JOIN crm.accounts c ON c.id = s.account_id
GROUP BY s.region
LIMIT 20;
Point Data dictionary path at a note; the panel parses it into a schema tree and uses it for result column tooltips. The expected shape (headings accept English or Chinese words):
## Database sales
- **Engine**: sqlite
- **Path**: /home/you/data/sales.db
- **Description**: what this database holds
### Table orders
One row per order.
| Column | Type | Description |
|---|---|---|
| id | INTEGER | primary key |
| created | TEXT | ISO `YYYY-MM-DD HH:MM:SS` |
| total | REAL | order total |
If the note does not exist yet, the panel offers to create a template.
The plugin looks for an installed Copilot or Claudian plugin. When it finds one it offers, once, to write a short instruction block describing the API:
await app.plugins.plugins["super-useful-sql"].api.run("SELECT 1 AS ok", { db: "sales" });
// → { columns, rows, ms, error, warning, truncated }
Also available: api.listDatabases(), api.dictionary(), api.instructions().
The block is written into the note set by Instructions file, wrapped in HTML-comment markers, so re-running replaces it instead of appending duplicates. Paste it wherever your assistant reads its context: a system prompt, a custom-prompt folder (Copilot's system-prompt folder is suggested automatically when detected), or your own context file. Nothing is written until you press the button or the notice action, and you can also just copy the block to the clipboard.
The databases are opened read-only, and that is enforced by the runtime rather than by parsing SQL:
| Guard | What it blocks |
|---|---|
new DatabaseSync(path, { readOnly: true }) |
any write to the main database |
PRAGMA query_only = 1 |
writes through attached databases |
setAuthorizer(...) allow-list (SELECT / READ / FUNCTION / RECURSIVE / TRANSACTION / SAVEPOINT) |
INSERT, UPDATE, CREATE, DROP, ATTACH, PRAGMA, … |
prepare(sql) compiles one statement only |
everything after the first ; never executes |
Text guard for VACUUM / ATTACH / DETACH |
statements the authorizer does not intercept (VACUUM INTO writes to disk) |
node:sqlite missing → refuse to run |
silently degrading to an unchecked path |
Writes inside an expression are not possible either: readfile()/writefile()/edit() are
sqlite3 shell functions and do not exist in the library this plugin uses, and a function-name
deny-list rejects load_extension and friends.
There is no subprocess and no shell: SQL is compiled and executed in-process, so none of the
sqlite3 command-line behaviours apply (dot-commands, option parsing, shell functions,
~/.sqliterc).
-wal / -shm sidecars). This is the point of the
plugin: keep large datasets out of the vault so the vault index and sync stay fast. No directory
is scanned and nothing is discovered automatically — only the exact paths you enter are opened,
and they are opened read-only.data.json (in the plugin folder),
capped and clearable (Settings → Clear history now) — not in your notes.LIMIT on large tables.ATTACH/DETACH/PRAGMA/VACUUM from the
query box (registered databases are attached for you).maxRows also caps exports. Raise it (or set 0, not recommended) if you need a full dump.Single-file, plain JavaScript — no build step. main.js is the source and the artifact.
# local install into a test vault
mkdir -p "<vault>/.obsidian/plugins/super-useful-sql"
cp main.js manifest.json styles.css "<vault>/.obsidian/plugins/super-useful-sql/"
# then reload Obsidian (Cmd/Ctrl+R) and enable the plugin
manifest.json — plugin metadata; versions.json maps plugin version → minimum app version.styles.css — panel styling.0.2.0, attach main.js, manifest.json and styles.css to the GitHub release.MIT — see LICENSE.