wiki-dashboard

Create dynamic, queryable Obsidian dashboard views using Bases or Dataview. Use for auto-updating tables, indexes, or structured views of vault content; not…

INSTALLATION
npx skills add https://github.com/ar9av/obsidian-wiki --skill wiki-dashboard
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Wiki Dashboard — Dynamic Vault Views

Two tools available: Obsidian Bases (native, GUI-driven, no plugin) and Dataview (community plugin, SQL-like, more powerful). Check which the user has and prefer Bases unless they ask for Dataview or need GROUP BY / computed columns.

Before You Start

Writing profile: Before drafting or rewriting natural-language Markdown, read and apply the Writing Profile Resolution section in llm-wiki/SKILL.md. Framework schema, provenance, safety, and operation-specific requirements take precedence.

Apply WRITING.md preferences only to optional Markdown dashboard prose; .base syntax remains unchanged.

  • Resolve config — follow the Config Resolution Protocol in llm-wiki/SKILL.md (inline @name override → walk up CWD for .env → global config → prompt setup). This gives OBSIDIAN_VAULT_PATH.
  • Read $OBSIDIAN_VAULT_PATH/index.md to understand what categories and pages exist.
  • Ask the user what they want to view if not specified — folder, tag, category, date range?
  • Ask if they have Dataview installed if you're unsure which tool to use.

Option A — Obsidian Bases ( .base files)

Bases are YAML files that define live views over vault notes. Native to Obsidian 1.8+, no plugin needed.

Official canonical schema

Top-level keys:

filters:      # Global filter applied to all views (expression strings under and/or/not)

formulas:     # Named computed properties — referenced as formula.<name>

properties:   # Display config per property — sets displayName for column headers

summaries:    # Aggregation formulas (e.g. mean, sum)

views:        # Array of view definitions (required)

Each item in views::

views:

  - type: table          # table | list | cards | map

    name: "View Name"    # display label

    limit: 50            # optional max rows

    order:               # column display order (list of property/formula names)

      - file.name

      - note.updated

    groupBy:             # grouping — goes INSIDE the view, NOT at top level

      property: note.tags

      direction: ASC     # ASC | DESC

    filters:             # view-specific filter (merges with global filters)

      and:

        - 'note.status != "done"'

    summaries:

      formula.myFormula: Average

Filter syntax — CRITICAL

Filters use expression strings, not typed objects. Always wrap in and:, or:, or not: — a bare list causes a "may only have one of and/or/not keys" parse error.

# CORRECT

filters:

  and:

    - file.inFolder("concepts")

# WRONG — typed objects (parse error)

filters:

  - type: folder

    folder: concepts

Filters support nesting:

filters:

  or:

    - file.hasTag("book")

    - and:

        - file.inFolder("concepts")

        - file.hasTag("research")

    - not:

        - file.hasTag("archived")

Property name conventions

Different contexts use different naming — confirmed from Obsidian's auto-reformat behaviour:

ContextFrontmatter field tagsFile nameFormula
properties: keysnote.tagsfile.nameformula.
order: valuestags (bare)file.nameformula.
groupBy.property:tags (bare)file.name—
filters: expressionsfile.hasTag(...) / note.tagsfile.nameformula.
formulas: expressionsnote.tags, note.updatedfile.name—

Basic table — folder filter

filters:

  and:

    - file.inFolder("concepts")

properties:

  file.name:

    displayName: Page

  note.tags:

    displayName: Tags

  note.summary:

    displayName: Summary

  note.updated:

    displayName: Updated

views:

  - type: table

    name: Table

    order:

      - file.name

      - tags

      - summary

      - updated

Cards view — folder filter

filters:

  and:

    - file.inFolder("entities")

properties:

  file.name:

    displayName: Entity

  note.title:

    displayName: Full Name

  note.tags:

    displayName: Tags

  note.summary:

    displayName: Summary

views:

  - type: cards

    name: Cards

    order:

      - file.name

      - title

      - tags

      - summary

Group by property — groupBy goes INSIDE the view

When groupBy is set, **omit that property from order:** — it becomes the group header row and adding it as a column too causes duplication.

filters:

  and:

    - file.inFolder("concepts")

properties:

  file.name:

    displayName: Concept

  note.summary:

    displayName: Summary

  note.updated:

    displayName: Updated

views:

  - type: table

    name: By Domain

    groupBy:

      property: tags        # bare property name, no note. prefix

      direction: ASC

    order:

      - file.name           # do NOT include tags here — already the group header

      - summary

      - updated

Tag filter

filters:

  and:

    - file.hasTag("machine-learning")

properties:

  file.name:

    displayName: Page

  note.category:

    displayName: Category

  note.summary:

    displayName: Summary

views:

  - type: table

    name: Table

    order:

      - file.name

      - category

      - summary

Multi-filter (folder AND tag)

filters:

  and:

    - file.inFolder("projects")

    - file.hasTag("active")

properties:

  file.name:

    displayName: Project

  note.summary:

    displayName: Summary

  note.updated:

    displayName: Last Updated

views:

  - type: cards

    name: Cards

    order:

      - file.name

      - summary

      - updated

OR filter (two folders)

filters:

  or:

    - file.inFolder("concepts")

    - file.inFolder("entities")

properties:

  file.name:

    displayName: Page

  note.category:

    displayName: Category

  note.updated:

    displayName: Updated

views:

  - type: table

    name: Table

    order:

      - file.name

      - category

      - updated

Computed column via formulas

filters:

  and:

    - file.inFolder("concepts")

formulas:

  days_stale: "floor((now() - note.updated) / 86400000)"

properties:

  file.name:

    displayName: Page

  note.updated:

    displayName: Updated

  formula.days_stale:

    displayName: Days Stale

views:

  - type: table

    name: Stale

    order:

      - file.name

      - updated

      - formula.days_stale

Filter expression reference

ExpressionWhat it does
file.inFolder("path")Pages in that folder
file.hasTag("tag")Pages with that tag (no # prefix)
file.hasLink("Note Name")Pages linking to a note
file.name == "note-name"Exact filename match
file.ext == "md"Filter by extension
note.propertyNameAny frontmatter property
formula.formulaNameA named formula result
now()Current timestamp in ms

On Obsidian UI-generated format: When Obsidian's GUI writes or reformats a .base file it may output a simplified shorthand with top-level columns:, sort:, and view: keys instead of the canonical schema. That format also works — Obsidian accepts both. Manually authored files should use the canonical schema above.

Option B — Dataview (community plugin)

Dataview uses a SQL-like query language inside dataview code blocks in any note. More powerful than Bases for computed columns, GROUP BY, and cross-folder queries.

Basic table — folder

TABLE

tags AS "Tags",

summary AS "Summary",

file.mtime AS "Last Modified"

FROM "concepts"

SORT file.mtime DESC

Table with clickable links (TABLE WITHOUT ID)

TABLE WITHOUT ID

file.link AS "Entity",

tags AS "Tags",

summary AS "Summary"

FROM "entities"

SORT file.name ASC

GROUP BY — use rows. prefix after grouping

After GROUP BY, individual file properties must be prefixed with rows. — otherwise the column is empty or errors.

TABLE WITHOUT ID

rows.file.link AS "Concept",

rows.summary AS "Summary"

FROM "concepts"

GROUP BY tags[0] AS "Domain"

Stale pages — use file.mtime for date math

Avoid choice(updated, date(updated), file.mtime) — mixed date formats in updated frontmatter cause arithmetic errors. file.mtime is always a valid DateTime.

TABLE WITHOUT ID

file.link AS "Page",

category AS "Type",

file.mtime AS "Last Modified",

(date(today) - file.mtime).days + " days" AS "Age"

FROM "concepts" OR "entities" OR "projects"

WHERE file.name != file.folder

WHERE (date(today) - file.mtime).days > 30

SORT (date(today) - file.mtime).days DESC

Multi-folder query

TABLE

summary AS "Summary",

file.mtime AS "Last Modified"

FROM "projects"

WHERE file.name != file.folder

SORT file.mtime DESC

Dataview reference

ClauseUsage
FROM "folder"All notes in folder
FROM #tagAll notes with tag
FROM "a" OR "b"Union of two folders
WHERE file.name != file.folderExclude folder index pages
GROUP BY field AS "Label"Group rows — use rows. for properties after this
SORT field DESCSort direction
file.linkClickable wikilink
file.mtimeLast modified time (always valid DateTime)
(date(today) - file.mtime).daysDays since last modification

Step 3: Write the File

Bases: Target path $OBSIDIAN_VAULT_PATH/_meta/<dashboard-name>.base

Dataview: Write queries directly into any .md note. A dedicated dashboard note at $OBSIDIAN_VAULT_PATH/_meta/dashboard.md works well for multi-section views.

Slug examples:

  • "All concepts" → _meta/concepts-index.base
  • "Recent ingests" → _meta/recent-ingests.base
  • "Project overview" → _meta/projects-overview.base
  • "Stale pages" → _meta/stale-pages.base
  • "Full dashboard" → _meta/dashboard.md

Create _meta/ if it doesn't exist yet.

Step 4: Embed Bases (optional)

To embed a .base inside a note:

## Entities

![[_meta/entities-tracker.base]]

Ask before modifying an existing note.

Step 5: Update Tracking

Append to $OBSIDIAN_VAULT_PATH/log.md:

- [TIMESTAMP] WIKI_DASHBOARD name="<slug>" tool=bases|dataview view=<type> filter="<description>"

No manifest or index update needed — dashboards are live queries, not static pages.

Common Dashboard Recipes

DashboardBest toolWhat it shows
Content indexBases or DataviewAll pages grouped by category, sorted by updated
Entity trackerBases (cards)Entity pages as a visual card gallery
Concepts by domainDataviewConcepts grouped by first tag using GROUP BY
Ingestion logEitherPages sorted by created date
Stale contentDataviewPages not touched in 30+ days with day count
Project overviewEitherProject pages with last-sync date
Research trackerDataviewSynthesis pages tagged research

Quality Checklist

  • Bases: filters use expression strings under and:/or:/not:, never typed objects
  • Bases: groupBy goes inside the view definition — not as a top-level key
  • Bases: column headers set via properties: <name>: displayName: "...", not columns: [{title}]
  • Bases: formulas: used for computed columns, referenced as formula.<name> in order/properties
  • Dataview: GROUP BY queries use rows.property not bare property
  • Dataview: date arithmetic uses file.mtime, not choice(updated, ...)
  • File written to _meta/ with a descriptive slug
  • log.md updated
  • User told how to embed Bases (![[_meta/<name>.base]]) or open the dashboard note

QMD Refresh After Vault Writes

QMD is a search index, not the source of truth. If $QMD_WIKI_COLLECTION is empty or unset, skip this step. Run it only after this skill has written or rewritten vault markdown. If QMD refresh fails, do not roll back the vault changes; report the QMD status separately.

Use $QMD_CLI if set; otherwise use qmd.

${QMD_CLI:-qmd} update

If the output says vectors are needed or embeddings may be stale, run:

${QMD_CLI:-qmd} embed

Verify the collection with either:

${QMD_CLI:-qmd} ls "$QMD_WIKI_COLLECTION"

or, when a specific page path is known:

${QMD_CLI:-qmd} get "qmd://$QMD_WIKI_COLLECTION/<page>.md" -l 5

Record one of:

  • QMD refreshed: update + embed + verified
  • QMD refreshed: update only + verified
  • QMD skipped: QMD_WIKI_COLLECTION unset
  • QMD skipped: qmd CLI unavailable
  • QMD failed: <short error summary>
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card