Obsidian Bases API for Plugins: What Works Now and What’s Still Missing

What the Obsidian Bases API Actually Gives You Right Now

Obsidian 1.10.0 shipped the first iteration of a public Bases API for plugin developers. If you were hoping for a clean app.bases.getViewFiles() call you can drop into any plugin, that doesn’t exist yet. What shipped is narrower: a mechanism for plugins to register custom view types inside Bases, not a general-purpose headless query runner.

That distinction matters a lot if you’re building something like a ranking tool or a cohort picker that wants to consume a Base result without surfacing a view to the user.

The registerBasesView() entry point

Call this.registerBasesView() from your plugin’s onload() and Obsidian registers your type alongside its built-in table, board, and gallery views. Inside that view class you get structured, stable access to query results — which is genuinely useful once you accept the constraint it imposes.

Results live at this.data.data, typed as BasesEntry[]. Each entry represents one matching file. You read individual property values with entry.getValue(propertyId). Grouped results sit at this.data.groupedData. No leaf-settling race, no polling internal state — just a typed array ready to iterate.

class MyBasesView extends BasesView {
  render() {
    const entries = this.data.data; // BasesEntry[]
    for (const entry of entries) {
      const file = entry.file; // TFile
      // process file here
    }
  }
}

The registerBasesView() call also returns false if Bases is disabled in the current vault, which gives you a clean way to guard capability at load time.

The public type surface in obsidian.d.ts

As of 1.10.x the relevant types are:

  • BasesEntry — one row in the result set. Carries file: TFile and a getValue(propertyId: string) method.
  • BasesQueryResult — the full output of a query run.
  • BasesView — the abstract class your custom view extends.
  • QueryController — executes queries and emits result updates.

What’s absent: any public method that runs a query against a user-specified .base file from outside a view. No app.bases.query(), no app.bases.getViewFiles(), nothing like it.

The gap: headless queries

The feature request that prompted this post asks for something conceptually simple. Given a .base file and a view name, return the matching notes:

const files = await app.bases.getViewFiles(baseFile, 'To Read');
// Promise<TFile[]>

That call doesn’t exist. A plugin that wants Base results today has two real options.

Option one: implement a BasesView and access this.data.data from within it. Clean and stable, but it forces your plugin into the view paradigm even when you have no UI to show.

Option two — the current workaround — open the .base file in a hidden leaf, wait for the QueryController to emit results, then read internal state and close the leaf:

const leaf = app.workspace.getLeaf(true);
await leaf.openFile(baseFile);
// wait for the controller to settle (poll or observe)
const controller = (leaf.view as any).controller;
const files = controller.results.map((e: any) => e.file);
leaf.detach();

This reaches into undocumented internal state that can change without notice. It spins up a full leaf even if the result is never shown. It is the definition of fragile, and the fact that real plugins are shipping with this code is a sign that the API surface needs to expand.

Why this matters for the broader plugin ecosystem

Bases is meant to be Obsidian’s native way to define a cohort of notes. A user who maintains a Base called Books.base with a view named To Read has already done the filtering work. Without a headless API, every plugin that wants to operate on a user-defined set of notes has to re-implement its own filter system — folders, tags, Dataview queries, plugin-specific query languages.

The practical cost: users maintain parallel definitions of the same set across multiple plugins. Dataview could express LIST FROM "Books.base#To Read" as naturally as it expresses a folder source. Tasks could scope task searches to a Base view. Spaced Repetition decks and Projects workspaces could both source from the same Base definition instead of requiring separate folder structures. Each plugin that can’t read a Base programmatically is asking users to duplicate their filtering logic.

What a richer API might look like

The minimal useful form is returning TFile[] — enough for plugins that only need file identity. A slightly richer version returns BasesEntry[] so callers can also read property values:

// minimal
app.bases.getViewFiles(baseFile: TFile, viewName: string): Promise<TFile[]>

// richer
app.bases.getViewEntries(baseFile: TFile, viewName: string): Promise<BasesEntry[]>

The underlying infrastructure already exists. QueryController runs this query every time a Base is opened. The missing piece is a stable public path to invoke it outside a view context — something the team could expose without fundamentally changing how Bases works internally.

Status as of mid-2026

The forum request is open. The 1.10 API was a real step forward — custom view types with structured result access are a solid foundation — but the headless query surface hasn’t shipped. If your plugin needs this, the most direct signal you can send is a vote on the feature thread. In the meantime, the registerBasesView() path is the cleanest option available if you can tolerate building a view around your logic.

Sources


Similar Posts