Habit tracker without Dataview
Dashy vault/Guides/Habit tracker without Dataview
Guides/Habit tracker without Dataview.md

A habit tracker in Obsidian without Dataview

Properties
blocks
statsheatmap
reads
checkbox properties in daily notes
needs dataview
checked on
Dashy 1.5.2

Tick a checkbox in each daily note. Two short blocks turn those ticks into a count, a streak, this week against last week, and a year of days. No Dataview and no JavaScript.

Four cards: 164 gym days, a longest streak of 14 days, 3 this week with +1 against last week, 19 this month; below them a year of days with the gym days in orange
What you have at the end of step 3. From a demo vault with a year of daily notes.

1Give every daily note a checkbox

One checkbox property per habit. In a daily note, add a property, set its type to Checkbox, and name it after the habit.

Diary/2026-09-14.md
---
gym: true
meditate: true
read: false
---

## 2026-09-14
  • 2026-09-14The note's name is its date. Anything after the date is fine: 2026-09-14 Monday counts too.
  • gym: trueA ticked box counts as 1, an unticked one as 0.
  • DiaryThe folder the blocks read from. Use the one your daily notes already live in.
Put the boxes in your template

Add the properties, unticked, to your daily note template. Every new day then starts with the boxes ready to tick.

2Count the days

Make a note for the tracker, say Habits.md, and paste this block. It is the top row of the picture above.

Habits.md
```stats
columns: 4
items:
  - { label: Gym days, source: Diary, field: gym, agg: sum, icon: 🏋️ }
  - { label: Longest gym streak, source: Diary, field: gym, agg: streak, unit: days, icon: 🔥 }
  - { label: Gym this week, source: Diary, field: gym, agg: sum, period: week, icon: 📅, compare: true, better: up }
  - { label: Gym this month, source: Diary, field: gym, agg: sum, period: month, icon: 🗓 }
```
  • agg: sumAdds up the ticks, so it counts the ticked days.
  • agg: streakThe longest run of ticked days on record. An unticked day breaks it, and so does a day with no note.
  • period: weekOnly this week, ending today. The week starts on Monday or Sunday, as your language has it.
  • compare: trueSets this week against the same days of last week. With better: up, a rise shows green.

3Paint the year

Paste this under the cards. Every ticked day gets a coloured cell. Click a cell to open that day's note.

Habits.md
```heatmap
source: Diary
field: gym
color: orange
title: Gym
```

Without range you get one grid per calendar year. range: 182d shows the last half year as one grid instead, which fits a phone better.

4The streak you are on

The longest streak is history. For the run you are on today, use current_streak. Here it is on a second habit, meditate:

Habits.md
```stats
columns: 2
items:
  - { label: Days in a row, source: Diary, field: meditate, agg: current_streak, unit: days, icon: 🔥 }
  - { label: Best streak, source: Diary, field: meditate, agg: streak, unit: days }
```
renders as
Two cards: 12 days in a row, and a best streak of 31 days

Today never breaks the run. Until you tick today's box, the count stops at yesterday. Only a missed yesterday ends the run.

The whole note

Everything above in one piece, ready to paste into an empty note.

Habits.md
```stats
columns: 4
items:
  - { label: Gym days, source: Diary, field: gym, agg: sum, icon: 🏋️ }
  - { label: Longest gym streak, source: Diary, field: gym, agg: streak, unit: days, icon: 🔥 }
  - { label: Gym this week, source: Diary, field: gym, agg: sum, period: week, icon: 📅, compare: true, better: up }
  - { label: Gym this month, source: Diary, field: gym, agg: sum, period: month, icon: 🗓 }
```

```heatmap
source: Diary
field: gym
color: orange
title: Gym
```

```stats
columns: 2
items:
  - { label: Days in a row, source: Diary, field: meditate, agg: current_streak, unit: days, icon: 🔥 }
  - { label: Best streak, source: Diary, field: meditate, agg: streak, unit: days }
```

The same tracker with Tracker or dataviewjs

If you searched for this, you have probably seen one of these. Both read the same gym: true notes.

With the Tracker plugin
```tracker
searchType: frontmatter
searchTarget: gym
folder: Diary
summary:
    template: "Gym days: {{sum()}}\nLongest streak: {{maxStreak()}} days\nCurrent streak: {{currentStreak()}} days"
```

```tracker
searchType: frontmatter
searchTarget: gym
folder: Diary
datasetName: Gym
month:
    startWeekOn: 'Mon'
```
  • Tracker reads true as 1 and false as 0, so sum() counts the ticked days.
  • The calendar is a month view: one month at a time, with arrows to the others.
  • The current streak counts back from the newest note Tracker finds, not from today, unless you set endDate.
  • Tracker's docs list no setting for "this week against last week".
With Dataview, JavaScript queries turned on
```dataviewjs
const days = dv.pages('"Diary"')
  .where(p => p.file.day && p.gym === true)
  .sort(p => p.file.day)
  .map(p => p.file.day)
  .array();

let best = 0, run = 0, prev = null;
for (const d of days) {
  run = prev && d.diff(prev, "days").days === 1 ? run + 1 : 1;
  best = Math.max(best, run);
  prev = d;
}
dv.paragraph(`Gym days: ${days.length}`);
dv.paragraph(`Longest streak: ${best} days`);
```
  • Needs Enable JavaScript queries in Dataview's settings. It is off by default.
  • This prints two lines of text. Cards, the week comparison and the year grid are more code, or another plugin.
  • Two notes on the same day reset this streak to 1. Fixing that is more code too.
DashyTrackerdataviewjs
Reads gym: trueYesYes, with searchType: frontmatterYes, you compare it in code
Count and longest streaksum, streaksum(), maxStreak()Write the loop
Current streakcurrent_streak, from todaycurrentStreak(), from the newest noteWrite it
This week against lastcompare: trueNot in the docsWrite it
CalendarA year of daysA month at a timeWrite it, or add a plugin
What you installDashyTrackerDataview, JavaScript queries on

Tracker also draws line, bar, pie and bullet charts from tags, text and inline fields. Dashy reads frontmatter only. If you track things that live in the text of your notes, Tracker is the better fit.

Variations

Two habits on one grid

Each habit in its own colour. A day with both shows the first one listed. Guide.

heatmap
```heatmap
source: Diary
layers:
  - field: gym
    color: orange
    label: Gym
  - field: read
    color: green
    label: Reading
```

Weekends off

Saturday and Sunday neither break the run nor add to it. Friday and Monday join up. Guide.

stats
```stats
items:
  - label: Workdays in a row
    source: Diary
    field: gym
    agg: current_streak
    days: weekdays
```

Holidays do not count

Mark a day with vacation: true. That day is skipped: it neither counts nor breaks the run.

stats
```stats
items:
  - label: Days in a row
    source: Diary
    field: gym
    agg: current_streak
    skip_field: vacation
```

A habit with a number

Steps instead of a tick. A day counts once it reaches the bar.

stats
```stats
items:
  - label: 8000+ steps, days in a row
    source: Diary
    field: steps
    agg: current_streak
    at_least: 8000
```

If a block shows an error

A block that cannot draw says why, inside the note. The usual three:

  • The field is missing. No note in Diary has gym. Check the spelling and the folder.
  • The field holds text. gym: done is text, not a checkbox. Set the property type to Checkbox.
  • No note has a date. A card with period says so when no note is named YYYY-MM-DD. If the date is a property, add date_field: date.