mdbasequery is an Obsidian Bases-compatible query engine for Markdown vaults.
It ships as:
- a CLI (
mdbasequery) for scripts and terminal workflows, - a TypeScript library for embedding query execution in applications.
Supported runtimes: Node.js 20+, Bun, and Deno 2.x.
You can run the CLI without installing globally:
npx mdbasequery --help
bunx mdbasequery --helpOr install globally:
npm install -g mdbasequery
# then
mdbasequery --helpCommand form:
mdbasequery [options]There is one command surface; all behavior is controlled through options.
mdbasequery supports three ways to provide the query definition:
.basefile via--base <path>- Inline YAML via
--yaml <yaml-text> - Flag-built query mode (
--filter,--select,--sort, etc.)
If --base/--yaml are not provided, the query is built from flags.
Run a .base file against a vault:
mdbasequery \
--base ./fixtures/queries/basic.base \
--dir ./fixtures/vaults/basic \
--format jsonUse inline YAML (heredoc to avoid shell escaping issues):
QUERY_YAML=$(cat <<'EOF'
views:
- type: table
name: default
filters: score >= 7
properties:
- title
- score
EOF
)
mdbasequery --yaml "$QUERY_YAML" --dir ./fixtures/vaults/basic --format mdUse CLI flags instead of YAML:
mdbasequery \
--dir ./fixtures/vaults/basic \
--filter "score >= 7" \
--select title \
--select score \
--sort score:desc \
--format csvSelect a specific view from a .base file and write output to disk:
mdbasequery \
--base ./fixtures/queries/grouped.base \
--view grouped \
--dir ./fixtures/vaults/basic \
--format yaml \
--out ./result.yamlDisable strict symbol/function checking:
mdbasequery --yaml "views: [{ type: table, name: default }]" --no-strict| Option | Type | Description |
|---|---|---|
--dir <path> |
string | Target directory to scan (default: current directory). |
--base <path> |
string | Path to Obsidian-style .base YAML file. |
--yaml <yaml-text> |
string | Inline YAML text. |
--view <name> |
string | View name to run (default: first view). |
| `--format <json | jsonl | yaml |
--out <path> |
string | Write serialized output to file instead of stdout. |
--strict |
flag | Enable strict mode (default behavior). |
--no-strict |
flag | Disable strict mode for unknown symbols/functions. |
--include <glob> |
repeatable string | Include path glob(s), matched against vault-relative paths. |
--exclude <glob> |
repeatable string | Exclude path glob(s), matched against vault-relative paths. |
--debug |
flag | Enable debug diagnostics in result metadata. |
--filter <expr> |
repeatable string | Add filter expression(s) in flag-built mode. |
--select <property> |
repeatable string | Select/project property/column in flag-built mode. |
| `--sort <prop:asc | desc>` | repeatable string |
--group-by <property> |
string | Group rows by one property in flag-built mode. |
--limit <n> |
number | Limit row count in flag-built mode. |
--version, -v |
flag | Show CLI version number. |
--help, -h |
flag | Show CLI help text. |
json: one structured object with rows, columns, summaries, stats, diagnostics.jsonl: one JSON object per row.yaml: YAML representation of the full result object.csv: projected columns in declared/selected order.md: Markdown table for quick human review.
- Exit code
0: successful query execution. - Exit code
1: fatal CLI/query error, or row-level diagnostics recorded as errors.
Diagnostics are returned as part of result metadata (and included in structured formats).
- Filters and formulas share the same expression language.
- Effective filter per view is
global filter AND view filter. - Formula dependencies are topologically ordered; cycles fail clearly.
- Strict mode is enabled by default.
- Summary formulas evaluate with
valuesbound to selected column values. - Output ordering is deterministic.
thiscontext in CLI/library mode is a file-like record of the base file when--base/basePathis used, otherwise an empty record at the vault root. Supports the documented Obsidian patterns (this.file.folder,file.hasLink(this.file)).
npm install mdbasequery
# or
bun add mdbasequeryimport { queryBase, serializeResult } from "mdbasequery";
const result = await queryBase({
dir: "./fixtures/vaults/basic",
yaml: `
views:
- type: table
name: default
filters: score >= 7
properties:
- title
- score
`.trim(),
strict: true,
});
console.log(result.rows.length);
console.log(serializeResult(result, "json"));Primary exports:
queryBase(options): Promise<QueryResult>parseBaseYaml(input): QuerySpeccompileQuery(spec, options?): CompiledQueryrunCompiledQuery(compiled, source?): Promise<QueryResult>serializeResult(result, format): string
Runtime adapters:
detectRuntimeAdapter()nodeAdapter,bunAdapter,denoAdapter
queryBase accepts a QueryBaseOptions object with:
spec?: QuerySpec- already parsed query spec.basePath?: string- path to.basefile.yaml?: string- inline YAML text.view?: string- selected view name.dir?: string- vault root directory.strict?: boolean- strict symbol/function behavior (default true).include?: string[]- include globs.exclude?: string[]- exclude globs.debug?: boolean- enable debug diagnostics.adapter?: RuntimeAdapter- runtime adapter injection.
Provide exactly one query source among spec, basePath, or yaml.
import { compileQuery, parseBaseYaml, runCompiledQuery } from "mdbasequery";
const spec = parseBaseYaml(`
views:
- type: table
name: default
filters: score >= 7
`.trim());
const compiled = compileQuery(spec, { strict: true });
const first = await runCompiledQuery(compiled, { dir: "./vault-a" });
const second = await runCompiledQuery(compiled, { dir: "./vault-b" });
console.log(first.rows.length, second.rows.length);QueryResult contains:
rows: row contexts withnote,file,formula,this, andprojectedfields.columns: projected column order.groups?: grouped rows when grouping is active. Group rows are projected records (same shape asrows[]entries); internal row data (file,note,this,formula,raw) is never exposed in serialised output.summaries?: computed summary values.stats: scan/match timing and counts.diagnostics: warnings and errors.
- Schema/YAML issues throw validation errors (for example from
parseBaseYaml). - Strict mode (default) rejects identifiers that exist nowhere in the vault (typo'd properties, undeclared
formula.Xreferences) and unknown functions with clear errors naming the identifier and the containing expression. Properties present on only some notes remain valid and evaluate as empty on notes that lack them. - Formula cycles throw explicit cycle errors.
- CLI returns exit code
1for failures and diagnostic errors.
- Bun (primary dev runtime)
- Node.js 20+
- Deno 2.x
bun installbun run build
bun run lint-typecheck
bun run test:bun
bun run test:node
bun run test:deno
bun run cibun run ci runs the required local validation suite:
- typecheck/lint
- Bun tests
- Node conformance tests
- Deno conformance tests
GitHub Actions runs:
lint-typechecktest-buntest-nodetest-denocompat-smoke-cli(matrix across Bun/Node/Deno)
src/core- schema, expression engine, query engine, serialization, indexing.src/runtime-adapters- runtime-specific filesystem/path adapters.src/cli.ts- CLI entrypoint.tests- Bun test suite (unit/integration/docs smoke).test-runtime- Node/Deno conformance tests.fixtures- vault and query fixtures..github/workflows/ci.yml- CI workflow definitions.
- Ensure
bun run cipasses locally. - Ensure GitHub Actions checks are green.
- Build artifacts (
dist) and package metadata are correct. - Publish to npm.
- Verify
npx mdbasequery --helpandbunx mdbasequery --helppost-release.