My Obsidian Daily Notes Template, 17 Months Later

Version 1.2 of my Obsidian Daily Notes Templater template: empty-day detection that ignores generated output, nested folder paths, configuration validation, and more reliable Markdown heading matching.

7 min read


Seventeen months ago I published an Obsidian Daily Notes template that solved one small problem: every new daily note searches backward through the vault and embeds the most recent # Work section with actual content.

It skips weekends, holidays, missing notes, and days where I created a note but left the Work section empty.

The original version worked until I started using it with nested folders, custom filenames, Windows line endings, less predictable Markdown headings, and notes containing the template’s own generated output.

Version 1.2.0 keeps the same workflow while fixing those assumptions. A generated note now looks like this:

# Today's Notes

# Previous Work
![[Daily Notes/2026-08-03-Monday#Work]]

# Work

The template counted its own output as work

In the original version, the generated embed landed inside the top-level # Work section:

# Work

Previous Work Items (Monday)
![[2026-08-03-Monday#Work]]

When the template inspected that note later, it found content under # Work and treated the day as populated. The lookup could not distinguish between something I had written and the history it had generated itself.

As a result, a day with no new work could still be selected as the most recent populated note.

Version 1.2 keeps generated history under # Previous Work and reserves # Work for content you type yourself. The lookup only reads the Work section.

Folder paths

The original built file paths manually:

const filePath = `${fileName}.md`;

That only works when daily notes live in the vault root.

Version 1.2 normalizes the configured folder before building the path. You can use an empty string for the root, a regular folder, a nested folder, or a path with a trailing slash:

const DAILY_NOTES_FOLDER = "";
const DAILY_NOTES_FOLDER = "Daily Notes";
const DAILY_NOTES_FOLDER = "Journal/2026";
const DAILY_NOTES_FOLDER = "Daily Notes/";

The generated embed now includes the complete vault-relative path:

![[Journal/2026/2026-08-03-Monday#Work]]

This also removes ambiguity when two folders contain notes with the same filename.

Vault APIs instead of the adapter

The first version used the global app object and read files through the vault adapter:

const content = await app.vault.adapter.read(filePath);

The updated version uses the Obsidian instance provided by Templater:

const file = tp.app.vault.getAbstractFileByPath(filePath);

if (file && file.extension === "md") {
    const content = await tp.app.vault.cachedRead(file);
}

The lookup now stays inside Obsidian’s normal Vault APIs. It also avoids checking whether a path exists and then performing a separate adapter read.

The change is not visible inside a daily note, but file lookup is more predictable and all paths remain vault-relative.

Configuration errors now produce warnings

Five constants sit at the top of the template:

const SECTION_NAME = "Work";
const DAILY_NOTES_FOLDER = "";
const FILENAME_FORMAT = "YYYY-MM-DD-dddd";
const DISPLAY_DATE_FORMAT = "YYYY-MM-DD";
const MAX_DAYS_TO_LOOK = 14;

The original version assumed all five were valid.

Version 1.2 checks that:

  • SECTION_NAME is a non-empty string
  • FILENAME_FORMAT is a non-empty string
  • DAILY_NOTES_FOLDER is a supported string value
  • MAX_DAYS_TO_LOOK is a positive integer
  • the current note title matches FILENAME_FORMAT

Filename parsing is strict. With the format above, 2026-08-04-Tuesday is valid. These are not:

2026-08-04
Tuesday-2026-08-04
2026-08-04-Tuesdaay

Previously, an invalid filename produced an invalid date and sent the template looking for paths that could not exist. It now stops and renders a warning inside the note:

> [!warning] Lookup skipped — configuration error
> - Cannot parse date from "2026-08-04" using FILENAME_FORMAT "YYYY-MM-DD-dddd"

The lookback value is validated as well. Starting with the day before the current note, the template checks up to MAX_DAYS_TO_LOOK previous calendar days. A note on the final day of that range is included.

When no populated note is found, the output includes the range that was searched:

Previous Work Items (none found in last 14 days, back to 2026-07-21)

Increase the value if you regularly take longer breaks, but keep it bounded so a configuration mistake does not scan the entire vault.

Heading matching

The original lookup expected a heading written exactly as # Work.

Valid Markdown can also contain:

#  Work
#	Work
# Work #

Custom section names created another problem. Names such as Work (Team A) and C++ contain regular-expression metacharacters, so inserting them directly into the matcher caused it to fail.

Version 1.2 escapes the configured section name first:

const escapedSection = SECTION_NAME.replace(
    /[.*+?^${}()|[\]\\]/g,
    "\\$&"
);

The matcher now supports:

  • LF and CRLF line endings
  • spaces or tabs after #
  • optional trailing hashes
  • sections at the beginning or end of a file
  • custom section names containing regular-expression characters

Only the next top-level heading closes the section.

In this example, ## Tickets and ## Notes both belong to # Work. The section ends at # Personal:

# Work

## Tickets

- DEV-123
- DEV-456

## Notes

Waiting for the API review.

# Personal

What counts as empty

A section containing only whitespace is empty.

A # Work section also counts as empty when the note’s only meaningful content appears under # Previous Work. Generated history is stored separately and ignored by the lookup.

Both cases are skipped, and the search continues backward. Subheadings are less clear-cut. A bare ## Tickets heading is still Markdown content, so a Work section containing only that heading may count as populated.

My rule is to add a subheading only when something goes beneath it.

Tests

The repository now includes a regression suite using Node’s built-in test runner:

node --test test/template.test.js

It covers:

  • populated sections
  • whitespace-only sections
  • generated Previous Work content
  • nested headings
  • LF and CRLF line endings
  • custom section names such as Work (Team A) and C++
  • root-level and nested folder paths
  • trailing-slash normalization
  • invalid configuration values

The test suite has no external dependencies. The Obsidian template remains a standalone Markdown file you can copy into your vault, while the tests live separately and verify the lookup logic.

Upgrading from the original version

Existing daily notes remain compatible, but you should replace the template file itself:

  1. Open your current template and save your configuration values.
  2. Copy the latest template.md.
  3. Reapply your folder, filename, section, and lookback configuration.
  4. Make sure generated output appears under # Previous Work.
  5. Keep your own notes beneath # Work.
  6. Create a test note and verify the generated embed.

Test at least these three situations:

  • a previous day with real Work content
  • a previous day with an empty Work section
  • a missing note between two populated notes

It is also worth checking the first Monday after installation, since the weekend gap exercises the date lookup across multiple missing days.

What changed in practice

The workflow is still the same. I create a daily note, the template finds my most recent work, and I open the note during standup instead of reconstructing the previous few days from several files.

Version 1.2 changes how reliably that lookup works:

  • empty days are skipped correctly
  • generated history does not count as new work
  • nested folders produce complete embed paths
  • invalid filenames produce visible warnings
  • custom headings and Windows line endings are supported
  • the lookup behavior has regression coverage

Most of the update went into path handling, validation, Markdown variations, and error reporting. The original lookup idea did not need to change.

What’s next

The scope will probably stay narrow. The template finds one configured section from the most recent populated daily note and embeds it into today’s note. It could be extended to track multiple sections, connect to a task system, or generate weekly summaries, but those are different workflows.

For now, I would rather keep this one focused on preparing for standup. Latest version, documentation, tests, and changelog: Obsidian Smart Daily Notes Template.

The original article explains the reasoning behind the workflow.