Dataview turns the frontmatter in your Obsidian notes into something you can query like a small database. For a developer vault, that means you can keep debt, decisions, incidents and review notes as plain Markdown files and still get live dashboards out of them.
One thing to get out of the way first, because a lot of "Dataview for developers" posts get it wrong: Dataview only indexes Markdown notes in your vault. It doesn't read .ts files, it doesn't parse imports, and there is no file.contents field to grep through. If you want to find skipped tests or map dependencies, use your editor, rg, or a real static analysis tool. Dataview is for the notes about your code.
This post assumes you know the basics (TABLE, LIST, TASK, FROM, WHERE). If not, the official docs are short and good.
1. Tech debt register, sorted by priority
Give each debt item its own note in a debt/ folder:
---
type: debt
area: auth
priority: 2 # 1 = fix now ... 4 = someday
status: open # open | in-progress | resolved
created: 2026-09-14
estimate_hours: 6
---
Then:
```dataview
TABLE area, priority, estimate_hours AS "Est. h", created
FROM "debt"
WHERE status != "resolved"
SORT priority ASC, created ASC
```
Use a number for priority. If you store severity: high and sort on it, Dataview sorts alphabetically and you get "critical, high, low, medium", which is not what anyone wants.
To see what you've actually paid down, add a resolved: 2026-09-20 field when you close an item:
```dataview
TABLE area, resolved, resolved - created AS "Took"
FROM "debt"
WHERE resolved
SORT resolved DESC
LIMIT 10
```
Subtracting two dates gives you a duration, which Dataview renders in a readable form. That only works if both fields are real dates, so write them as plain ISO dates (created: 2026-09-14). A quoted date in an inline field (created:: "2026-09-14") stays a string, and so does anything not in ISO form; resolved - created then silently renders nothing. If a field comes through as text, wrap it in the query: date(resolved) - date(created).
2. ADRs that are due for a second look
Architecture decision records go stale quietly. The decision was right at the time; the constraints moved. A reviewed date in the frontmatter lets you surface old ones:
---
type: adr
status: accepted # proposed | accepted | superseded | rejected
decided: 2025-06-02
reviewed: 2025-06-02
superseded_by:
---
```dataview
TABLE decided, reviewed, choice(reviewed < date(today) - dur(1 year), "revisit", "ok") AS "Freshness"
FROM "decisions"
WHERE status = "accepted"
SORT reviewed ASC
```
choice(condition, ifTrue, ifFalse) takes exactly three arguments. If you want three buckets, nest it:
choice(reviewed < date(today) - dur(1 year), "revisit",
choice(reviewed < date(today) - dur(6 months), "soon", "ok"))
"Revisit" doesn't mean "wrong". It means "read this again and bump reviewed if it still holds."
3. Incidents with open follow-ups
Incidents get written up; the action items are what get forgotten. If your incident notes use checkboxes for follow-ups, a TASK query pulls every unfinished one into a single list:
```dataview
TASK
FROM "incidents"
WHERE !completed AND status != "-"
GROUP BY file.link
```
And a table view for the incidents themselves:
```dataview
TABLE severity, service, occurred, length(filter(file.tasks, (t) => !t.completed AND t.status != "-")) AS "Open items"
FROM "incidents"
WHERE length(filter(file.tasks, (t) => !t.completed AND t.status != "-")) > 0
SORT occurred DESC
```
No manual action_items_pending: 3 field to keep in sync. The count comes from the checkboxes.
Why status != "-": Dataview only counts [x] as completed, so !completed on its own also matches cancelled tasks. The Tasks plugin (and many themes) mark those [-], and status is the character between the brackets. If you use a different character for cancelled, swap it in.
4. PR reviews still waiting on something
If you keep a short note per PR you're reviewing (or authored and are waiting on), a status field is enough:
---
type: pr
repo: api
pr: 1234
status: waiting # waiting | changes-requested | approved | merged
opened: 2026-09-18
---
```dataview
TABLE repo, pr, status, opened
FROM "reviews"
WHERE status = "waiting" OR status = "changes-requested"
SORT opened ASC
```
Want to flag the old ones? Filter on age instead of storing a days_in_review number you'd have to update by hand:
WHERE status = "waiting" AND opened < date(today) - dur(3 days)
5. Unfinished tasks from recent daily notes
This is the one I'd keep open most often. If your daily notes have a date in the filename (2026-09-27.md), Dataview exposes it as file.day:
```dataview
TASK
FROM "daily"
WHERE !completed AND status != "-" AND file.day >= date(today) - dur(14 days)
GROUP BY file.link
SORT file.day DESC
```
Anything you wrote down as a to-do in the last two weeks and never ticked off shows up here, grouped by the day you wrote it.
6. Orphan notes
Notes with no links in or out are usually either finished thoughts that never got connected, or junk:
```dataview
LIST
FROM "" AND -"90 Templates"
WHERE length(file.inlinks) = 0 AND length(file.outlinks) = 0 AND exclude-from-orphans != true
SORT file.mtime ASC
```
Template files and daily notes are usually unlinked on purpose. Prefer an exclude-from-orphans: true frontmatter flag over hardcoding folder names: a rename in the vault can't silently reintroduce them. Put the flag on dailies, weeklies, dashboards and scratchpads; keep the Templates folder out of the query by path (anything in a template's properties gets copied into notes made from it).
I'd run this during a weekly review, not keep it on a dashboard. Either link them to something or delete them.
7. What changed this week
Useful for writing a weekly summary or standup notes:
```dataview
TABLE file.folder AS "Folder", file.mtime AS "Modified"
FROM ""
WHERE file.mtime >= date(today) - dur(7 days)
SORT file.mtime DESC
```
One catch: file.mtime is the file's modification time on disk, and sync tools (Obsidian Sync, iCloud, Syncthing) can bump it when they write a note to another device. After a sync, this list can fill up with notes you didn't touch. If that happens, stamp a modified: field in the frontmatter and query that instead, falling back to file.mtime for notes that don't have it yet:
```dataview
TABLE file.folder AS "Folder", default(modified, file.mtime) AS "Modified"
FROM ""
WHERE default(modified, file.mtime) >= date(today) - dur(7 days)
SORT default(modified, file.mtime) DESC
```
The Linter plugin's "YAML timestamp" rule can write the field for you, or you can use a Templater hook. With Linter, set the modified key to modified, the format to YYYY-MM-DDTHH:mm:ss (the default format isn't one Dataview reads as a date), and "Date modified source of truth" to "user or Linter edits" (the default copies the file system time, which is the thing sync bumps).
On a synced vault, add a short grace window so mid-sync touches don't reorder everything by a few seconds. Cap recent edits to a floor age (for example five minutes) before sorting:
```dataview
TABLE file.folder AS "Folder", default(modified, file.mtime) AS "Modified"
FROM ""
WHERE default(modified, file.mtime) >= date(today) - dur(7 days)
SORT (date(now) - default(modified, file.mtime) < dur(5 minutes) ? dur(5 minutes) : (date(now) - default(modified, file.mtime))) ASC, file.name ASC
```
Notes touched inside the window sit together at the top; change dur(5 minutes) if you want a wider or tighter floor.
Things that trip people up
-
Dates must look like dates. Frontmatter values like
2026-09-27are parsed as dates.Sept 27is just text and date comparisons silently return nothing. Quoted dates in inline fields (due:: "2026-09-27") are text too: leave the quotes off, or wrap the field indate()in the query. -
Field names are normalized. A field called
Due Datebecomesdue-datein queries. Lowercase, no spaces saves you some confusion. -
Test
FROMfirst. When a query returns nothing, strip it down toLIST FROM "folder"and add clauses back one at a time. -
Narrow
FROMin big vaults.FROM ""scans everything. Pointing at a folder or tag keeps dashboards snappy. -
Keep conventions written down. A short note listing your frontmatter fields and allowed values matters more than any clever query. Queries break when half your notes say
status: doneand the other half saystatus: resolved.
Where to start
Don't build all of this at once. Pick the one query that answers a question you actually ask every week (for most people that's #5 or #1), add the frontmatter to new notes going forward, and let the dashboard fill up. Backfilling old notes is rarely worth it.
Thanks to @contentclips_st on dev.to for the sync, date, cancelled-task, orphan-flag and grace-window fixes.
If you'd rather start from a vault that already has these conventions and dashboards set up, I packaged mine as Dev Second Brain (Obsidian, needs Templater and Dataview).
Top comments (8)
Solid list — the numeric priority advice is the underrated part; I've seen more dashboards broken by
SORT severitythan by anything else.A few additions from running similar registers:
#7
file.mtimehas a sync gotcha. Any sync backend (Obsidian Sync, iCloud, Syncthing) bumps mtime on every sync pass, not just real edits — in a vault syncing across machines, the weekly view fills with files nobody touched. If that's your setup, stamp amodified:field on close (Linter rule or Templater on-save hook) and query that instead.resolved - createdfails silently on quoted dates.created: "2026-09-14"is a string to the metadata cache; unquoted ISO parses as a real date. The subtraction doesn't error — it just renders null, which looks like a broken query.WHERE !completedalso catches cancelled tasks. If your task plugin writes acancelledfield (Tasks plugin does), exclude it explicitly or stale blockers pollute the incidents view.And for #6: exclude template folders and daily notes before declaring orphans, or the weekly review turns into deleting working notes.
Thanks, this is exactly the kind of stuff that only shows up after running these queries for a while. The mtime one bit me too. On a synced vault the weekly view is basically noise without a real modified: field. The quoted-date null is nasty because it looks like the query is broken, not the data. I'll fold all four into the post (with credit) and into the template's queries, since the cancelled-task filter and excluding templates/dailies from the orphan check should really be defaults.
The credit is more than fair. Two small things I'd bake in from hard experience:
On the weekly view, anchor to a real
modified:rather than file mtime — but add a grace window (e.g. ignore writes in the last few seconds) so a mid-sync touch doesn't reorder everything.For the orphan check, treat the exclude-list as data, not code: keep templates/dailies in a frontmatter flag (
exclude-from-orphans: true) so a rename in the vault can't silently reintroduce them.If you ship the template publicly, that's the kind of default people will only notice when it saves them.
Thanks for the thoughtful hard-earned suggestions. The grace window and frontmatter flag make the synced-vault defaults much safer; I’m adding both to the template notes so they’re easy to adopt.
Take your time — the update will be worth it. If you want the cancelled-task exclusion as a standalone snippet when you write it up, say the word and I'll paste one. Subscribed, curious how the weekly view reads once real modified: data is in the template.
Both good calls. The frontmatter flag is better than a hardcoded folder list, since a renamed folder would quietly bring templates back into the orphan check. I'm going to try exclude-from-orphans: true in the next template update, and test a grace window on the weekly view against a synced vault.
And yes please on the cancelled-task snippet. I'd like to compare it with what I put in the post.
Thanks for sticking with this thread, it's made the post a lot better.
Here you go — the cancelled-task snippet I mentioned. Principle: cancelled is not done — it stays visible for the weekly review but never counts as open work:
Tag beats the - [-] status symbol: plain-Markdown readable, survives sync conflicts. Want them listed separately? A second query with WHERE contains(tags, "#cancelled") only — two cheap DQL passes, zero dataviewjs. Your grace-window test against a synced vault is the right last mile — sync lag is exactly where these queries break. Comparing with your post's version sounds great.
Thanks for pasting that — clear and useful.
I went with
status != "-"in the post (Tasks-plugin cancelled[-]), since that's what most of the vaults I see already use. Your#cancelledtag version is the better default for plain-Markdown vaults that don't run Tasks, and the "list cancelled separately" second query is a nice weekly-review trick I'll borrow.I'll keep both approaches in the notes for the next template pass. Appreciate you shipping the snippet.