← 개발일지

Build a Recurring Meeting Template in Obsidian with Templater


Your Meeting History Is Only as Good as Your Template

You run a monthly meetup — a book club, a study group, a lightning-talk circle. Two years in, someone asks "remember that talk about X?" and nobody can recall who gave it or when. Without structured notes, years of collective insight just evaporate.

This guide walks you through designing a Templater template for recurring meetings in Obsidian. Not a copy-paste snippet, but the actual design decisions you'll face and how to think through them. The patterns here work for any recurring event: reading groups, mastermind sessions, retrospectives, dinner clubs.

Prerequisites

You need Obsidian with the Templater community plugin enabled and a designated template folder (e.g., _templates). The Dataview plugin is optional but recommended — structured frontmatter pays off once you have 10+ entries to query.

Design Decisions Before You Write Code

Skip these and you'll be refactoring your template six months in.

What's Your Primary Key?

You have three realistic options for filenames:

Session number (Meetup 48): Feels satisfying. But if you ever try to backfill old meetings and you're not sure which months you skipped, your session numbers become guesses. Guessed data erodes trust in your archive.

Date (Meetup 2026-04): Fact-based, auto-sortable, guaranteed unique for monthly events. You can't get it wrong.

Both (Meetup 48 (2026-04)): Verbose, and the session-number problem remains.

The recommended approach: use YYYY-MM as the primary key in filenames, and keep session numbers as auto-calculated metadata in frontmatter. Calculate it as "number of existing files + 1" — this stays accurate regardless of what order you create past entries.

Folder Structure

Under ~100 files, a flat folder works fine. If your group has been meeting for years, year-based subfolders make browsing easier. Dataview queries traverse subfolders recursively, so splitting by year costs you nothing functionally.

meetings/
├── 2024/
│   ├── Meetup 2024-01.md
│   └── Meetup 2024-03.md    ← February was skipped
├── 2025/
└── 2026/
    └── Meetup 2026-04.md

Your Templater script can auto-create year folders using app.vault.createFolder() — check existence first with getAbstractFileByPath() to avoid errors.

Frontmatter That Actually Enables Queries

If you ever want to answer "who presented most often?" or "what topics keep coming up?", your frontmatter structure matters. The key insight: bundle presenter and topic as an object list, not parallel arrays.

# Fragile — relies on index alignment
presenters: [Alice, Bob]
topics: [AI Ethics, Sourdough]

# Robust — mapping is explicit
presentations:
  - presenter: Alice
    topic: "AI Ethics"
  - presenter: Bob
    topic: "Sourdough"

With the second structure, Dataview can query flat(presentations).presenter and the mapping stays intact.

Core Implementation Patterns

Dual Mode: New Entry vs. Backfill

Long-running groups inevitably want to recover past records. Use tp.system.suggester() to let the user choose between creating a note for today or entering a past date manually.

const mode = await tp.system.suggester(
  ["📝 This month (today's date)", "📂 Backfill past meeting"],
  ["new", "restore"]
);

let targetDate;
if (mode === "restore") {
  const input = await tp.system.prompt("Meeting date (YYYY-MM)", "2024-01");
  targetDate = moment(input + "-01");
} else {
  targetDate = moment();
}

Auto-Calculated Session Numbers

Filter files by regex, count them. Since the filename doesn't contain the session number (the primary key is YYYY-MM), you use length + 1 rather than parsing a max value.

const allFiles = app.vault.getMarkdownFiles()
  .filter(f => f.path.startsWith(ROOT_FOLDER + "/")
            && /^Meetup \d{4}-\d{2}$/.test(f.basename));
const session = allFiles.length + 1;

Previous/Next Navigation

Extract all months from existing files, sort them, and find the immediate predecessor and successor of the current month. This handles skipped months correctly.

const allMonths = allFiles
  .map(f => f.basename.match(/(\d{4}-\d{2})/)?.[1])
  .filter(Boolean)
  .sort();

const prevMonth = allMonths.filter(m => m < monthKey).pop();
const nextMonth = allMonths.filter(m => m > monthKey)[0];

The "next" link will be a dead link at creation time. When you create next month's note, Obsidian auto-resolves it — free bidirectional navigation with zero maintenance.

Duplicate Prevention

Guard against accidentally running the template twice in the same month:

const existing = app.vault.getMarkdownFiles()
  .find(f => f.basename === expectedFilename);
if (existing) {
  new Notice(`⚠️ ${expectedFilename} already exists.`);
  return;
}

Limitations to Know About

Backfilled entries don't update existing notes' nav links. Templater can only modify the note it's currently creating. If you backfill ten past meetings at once, each note's prev/next links reflect the state at creation time. Practically, this rarely matters. If it bothers you, a Linter plugin rule or a batch script can fix links after the fact.

Session numbers reflect creation order, not chronological order. If you backfill 2024-01 first and 2023-06 second, the numbers won't match the timeline. The month field in frontmatter is the real identifier — session number inconsistency is cosmetically annoying but functionally harmless. Backfill chronologically if you want clean numbers.

Watch your tR += newlines. When generating dynamic YAML lists inside `` blocks, a missing \n will break your frontmatter silently. Always double-check the raw output of dynamically generated YAML sections.

What to Build Next

Once you have 10+ entries, create a Dataview dashboard. With structured frontmatter, you can surface presenter frequency, topic trends, attendance patterns — all automatically. Ten meetings is data; fifty meetings is institutional memory.

The template is just the first investment. The compound returns come from consistency.