A habit tracker in Obsidian needs three things: a place to record the habit, a way to read those records back, and a view that makes a streak obvious. With the Heatmap Tracker plugin, the first is a line of frontmatter in your daily note, the second and third are a three-line codeblock. No spreadsheet, no external app, no account.
1. Record the habit in your daily note
Anything you can put in frontmatter can be tracked. Booleans are the
natural fit for a yes/no habit and count as 1; numbers
let you record a quantity as well as the fact that it happened.
---
meditated: true
exercise: 45
water: 6
pages-read: 30
---
Felt sluggish this morning, better after the walk.
The note's filename is what dates the entry, so keep the usual
YYYY-MM-DD convention. If you use Obsidian's core
Daily notes plugin with a template, add the habit keys to
that template once and every new day starts with the fields ready to
fill.
2. Render the tracker
Add a codeblock wherever you want the tracker to live — a dashboard note is the usual place. One property, one heatmap:
```heatmap-tracker
property: meditated
```
Prefer not to write it by hand? Run Insert Heatmap Tracker from the command palette; the modal builds the codeblock and inserts it at the cursor.
3. Read the streaks
Each square is a day. Filled means the habit happened, and the colour gets stronger as the value does. Two things make the grid worth more than a checklist:
- Gaps are visible. A monthly total of 22 hides whether you missed a day here and there or fell off for a week. The grid shows which one it was.
- Every square is a link. Click a filled day to open that note. Click an empty one and the plugin offers to create it, so backfilling is one click.
The Statistics tab next to the heatmap keeps the numbers a habit tracker actually needs — total, days tracked, and your longest unbroken run.
Tracking several habits at once
Two approaches, and they combine. Put one codeblock per habit on a dashboard note to get a separate grid for each, or aggregate related habits into one grid by passing an array:
```heatmap-tracker
property: [running, cycling, swimming]
```
Aggregation answers "did I move today", not "which sport" — pick it when consistency matters more than the breakdown.
Only counting the right notes
By default the plugin searches your Daily Notes folder, falling back to the whole vault. If your logs live somewhere else, or other notes happen to use the same property name, narrow the query:
```heatmap-tracker
property: meditated
path: "journal/daily"
tags: [habit]
filters:
- property: status
operator: equals
value: done
```
| Parameter | What it does |
|---|---|
path |
Folder to search in. |
tags |
Only notes carrying at least one of these tags. |
filters |
Extra frontmatter conditions — equals,
contains or notEmpty. All must
match.
|
Making the colours mean something
A boolean habit only ever has one filled level, which is fine. A numeric one benefits from a scale that matches the range you care about. Reading minutes between 30 and 120, for example:
```dataviewjs
const trackerData = {
entries: [],
heatmapTitle: "Reading",
intensityConfig: { scaleStart: 30, scaleEnd: 120 },
};
// fill trackerData.entries from your notes, then:
renderHeatmapTracker(this.container, trackerData);
```
Without it, a year where most days sit between 30 and 40 minutes renders as one flat colour. See the Dataview guide for the full scripted form.
When the grid comes up empty
- Is Dataview installed and enabled? Heatmap Tracker reads notes through it.
-
Does the property name match exactly?
Meditatedandmeditatedare different keys. -
Are the notes in the searched folder? Set
pathif they are not. -
Is the year right? The heatmap opens on the current year;
use the arrows or set
year.
Since v2.8.0 a codeblock that cannot render says why in the note itself rather than failing silently, and a query that matched nothing tells you which property and path it looked for.