Before, every morning I drowned in tasks and thoughts: what to do right now, who to reply to, what priority my tasks had and what was I even thinking about a couple of days ago? Now I open one note — and immediately see all my projects, tasks, habits and thoughts. In this article I’ll go through my whole daily-notes system in detail, step by step and in the simplest language — so that you can assemble it for yourself literally in an evening.


Why keep a diary in Obsidian at all

A paper notebook is wonderful. But it has one downside: what you wrote yesterday stays “yesterday.”

Tasks don’t carry over by themselves, habits don’t add up into a chart, and thoughts get lost between the pages.

In Obsidian it’s all different. The daily note becomes the centre of your day:

🔁
Tasks
carry over by themselves and come from projects
📅
Calendar
syncing with Google Calendar
Habits
buttons and progress charts
💭
Thoughts
a free field for ideas and reflections

Waking up in the morning, I immediately remember what I was busy with yesterday. Uncompleted tasks are automatically carried over to today. Simple routines — habits, meditation — I mark with one button, and they add up into clear dynamics. And I take new tasks straight from my projects, which are linked to my goals.

This simple system keeps me focused: I do what’s really needed, rather than procrastinating. Let’s build it together.

A full description of my system and the PARA structure is in the previous materials on the site.


Step 1. What you need to install

To keep tasks, thoughts and habits in Obsidian, we’ll need a few plugins and one built-in setting.

Community plugins

We open Settings → Community plugins → Browse and install one by one:

PluginWhat it’s for
DataviewGathers data from notes (e.g. thoughts by day, charts)
Meta BindTurns note properties into buttons and fields right in the text
HomepageMakes a home page that opens on launch
TemplaterRuns scripts when a note is created (carrying over tasks)
ButtonsCreates buttons that launch commands in one click
QuickAddThe “brain” of the buttons: runs macros and scripts

After installing Dataview, go into its settings and enable all the checkboxes (especially Enable JavaScript Queries and Enable Inline JavaScript Queries) — without this the calendar widget won't work.

Enabling the built-in “Daily notes”

Obsidian can keep a diary right “out of the box.” We go to Settings → Core plugins and enable Daily notes. Then we configure them for ourselves.

Three key settings:

  1. The note-title format. For me it’s just the day, month and year:

    DD-MM-YYYY
    

    This matters: all my code (the widget, carrying over tasks) looks for notes precisely in the DD-MM-YYYY format. If you want to add the day of the week to the title — add the letters (e.g. dddd), but then fix the format in the code too. Which letter is responsible for what is handy to look up on format.cm (the Moment.js documentation).

  2. The folder for new notes. For me the daily notes are in 2. Areas/Дневники/Ежедневные заметки. I keep everything by the PARA structure, and keeping a diary is an “area of life” (Area), so its place is in the Areas folder.

  1. The template. This is the structure by which each new note will be created. My template is in 0. Files/4. Templates/Шаблон ежедневных заметок. “Templates” means templates.

This is what the structure of my daily note looks like:

Properties → week/calendar widget → plans for the day → carrying over tasks → habits → food → thoughts. It’s not always possible to fill in everything. But the very striving makes me more disciplined.

For those who don't want to figure it out for ages

Try my ready-made Obsidian template and start systematising your information today

Learn about the template

Step 2. The note’s properties (YAML)

Let’s open the template and go through it from top to bottom. The first thing we see is the note’s properties (they’re also called YAML or frontmatter).

What’s their point? Properties are the “official” data of a note, which other plugins read easily. For example, the habits chart takes its information precisely from the properties, not from the ordinary text. This rule is worth remembering: what’s important for charts and automation — store in the properties.

Properties are written at the very top of the note between two lines of three dashes:

---
Спорт: false
Чтение: false
Прогулка: false
Завтрак:
Перекусы:
Обед:
Ужин:
Итого_ккал:
---

Here there are two groups:

  • Habits (Спорт, Чтение, Прогулка) — the “checkbox” type. false means “not yet done.”
  • Food (Завтрак, Обед, Ужин, Перекусы, Итого_ккал) — the “text” type, here we write down what we ate, and a calorie counter.

Adding your own property is simple: in properties mode click "+ Add property", type the name and choose the type (text, number, checkbox, date). After a couple of days of filling it in, you'll already see the dynamics on a chart.


Step 3. Syncing with Google Calendar and tasks

Right under the properties in the template comes a big block of code. Don’t be scared — it was written by an AI, and I just pasted it. This code draws a beautiful widget: it shows the day of the week, the progress for the week, and in “Tasks” mode — syncs with your Google Calendar and Google Tasks. Right from Obsidian you see the events and tasks for the day, you can mark them as done and add new ones.

For this to work, the code in the template alone isn’t enough. You need to give Obsidian access to your Google account. This is done via the free Google Apps Script service. Let’s go through it step by step — slowly and in detail.

3.1. Preparing the server on scripts.google.com

1
Open script.google.com and click "New project".
2
Delete all the text in the editor and paste the code I give below.
3
Connect the Google Tasks API service: on the left in the "Services" menu (the "+" icon) find Tasks API and click "Add". Without this step the code won't see your tasks.

Step 3 is skipped most often. The code uses Tasks.Tasklists.list(), and that's an "advanced service." If you don't add Tasks API, the script will crash with an error.

Here’s the code you need to paste into the editor (it’s responsible for reading events/tasks and for creating/closing tasks):

// ======================================
// GOOGLE APPS SCRIPT — TASKS + CALENDAR
// ======================================
 
// Authorisation check (run once manually)
function testAuth() {
  const taskLists = Tasks.Tasklists.list();
  Logger.log(taskLists);
  const events = CalendarApp.getDefaultCalendar().getEvents(new Date(), new Date());
  Logger.log(events.length + " events today");
}
 
// ====== READING: calendar events + tasks for the day ======
function doGet(e) {
  try {
    const tz = Session.getScriptTimeZone();
 
    // DEBUG mode: open GAS_URL?debug=1 in the browser — it will show ALL tasks
    if (e && e.parameter && e.parameter.debug) {
      const debugLists = (Tasks.Tasklists.list().items) || [];
      const all = [];
      debugLists.forEach(list => {
        const items = (Tasks.Tasks.list(list.id, {
          showCompleted: true, showHidden: true, maxResults: 100
        }).items) || [];
        items.forEach(t => {
          all.push({ list: list.title, listId: list.id, title: t.title, due: t.due || null, status: t.status });
        });
      });
      return ContentService
        .createTextOutput(JSON.stringify({
          success: true, scriptTimeZone: tz,
          listsFound: debugLists.map(l => l.title),
          totalTasks: all.length, all: all
        }))
        .setMimeType(ContentService.MimeType.JSON);
    }
 
    const param = (e && e.parameter && e.parameter.date) ? e.parameter.date : null;
    const base = param
      ? new Date(+param.split("-")[0], +param.split("-")[1] - 1, +param.split("-")[2])
      : new Date();
 
    const start = new Date(base.getFullYear(), base.getMonth(), base.getDate(), 0, 0, 0);
    const end   = new Date(base.getFullYear(), base.getMonth(), base.getDate(), 23, 59, 59);
    const dayStr = Utilities.formatDate(base, tz, "yyyy-MM-dd");
 
    const events = CalendarApp.getDefaultCalendar().getEvents(start, end).map(ev => ({
      title: ev.getTitle(),
      allDay: ev.isAllDayEvent(),
      time: ev.isAllDayEvent() ? null : Utilities.formatDate(ev.getStartTime(), tz, "HH:mm")
    }));
 
    const tasks = [];
    const lists = (Tasks.Tasklists.list().items) || [];
    const today = new Date();
    const todayStr = Utilities.formatDate(today, tz, "yyyy-MM-dd");
    const isToday = dayStr === todayStr;
 
    lists.forEach(list => {
      const items = (Tasks.Tasks.list(list.id, { showCompleted: true, showHidden: true }).items) || [];
      items.forEach(t => {
        if (!t.due) return;
        const taskDay = t.due.substring(0, 10);
        const isExactDay = taskDay === dayStr;
        const isOverdue = isToday && taskDay < dayStr && t.status === "needsAction";
        if (isExactDay || isOverdue) {
          tasks.push({
            id: t.id, listId: list.id, title: t.title,
            status: t.status, completed: t.completed || null, overdue: isOverdue
          });
        }
      });
    });
 
    tasks.sort((a, b) => (a.overdue === b.overdue) ? 0 : (a.overdue ? -1 : 1));
 
    return ContentService
      .createTextOutput(JSON.stringify({ success: true, date: dayStr, events, tasks }))
      .setMimeType(ContentService.MimeType.JSON);
 
  } catch (error) {
    return ContentService
      .createTextOutput(JSON.stringify({ success: false, error: error.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}
 
// ====== WRITING: create OR close a task ======
function doPost(e) {
  try {
    const data = JSON.parse(e.postData.contents);
 
    if (data.action === "complete") {
      const listId = data.listId || Tasks.Tasklists.list().items[0].id;
      Tasks.Tasks.patch({ status: "completed" }, listId, data.taskId);
      return ContentService
        .createTextOutput(JSON.stringify({ success: true, message: 'Task done!' }))
        .setMimeType(ContentService.MimeType.JSON);
    }
 
    const title = data.title;
    const dueDate = data.dueDate;
    const taskLists = Tasks.Tasklists.list();
    const defaultTaskList = taskLists.items[0].id;
 
    const task = Tasks.Tasks.insert({ title: title, due: dueDate, status: 'needsAction' }, defaultTaskList);
 
    return ContentService
      .createTextOutput(JSON.stringify({ success: true, taskId: task.id, message: 'Task added!' }))
      .setMimeType(ContentService.MimeType.JSON);
 
  } catch (error) {
    return ContentService
      .createTextOutput(JSON.stringify({ success: false, error: error.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

3.2. Giving permission for the first time

Google won’t let the code touch your calendar until you personally allow it. Let’s do this once manually:

1
In the function dropdown at the top, choose testAuth and click "Run" (▶).
2
An "Authorisation required" window will appear → click "Review permissions" and choose your Google account.
3
A scary screen "Google hasn't verified this app" will appear. This is normal — the app is your own. Click at the bottom "Advanced" → "Go to … (unsafe)".
4
Click "Allow". Done — access is granted. In the Logs you'll see your task lists.

The "Google hasn't verified the app" screen appears for everyone's personal scripts. You aren't passing data to anyone outside — the code works only in your account.

Now let’s make a real web address from the code, which Obsidian will access:

1
At the top right click "Deploy" → "New deployment".
2
Click the type-selection gear and choose "Web app".
3
Fill in:
Execute as: "Me" (your account)
Who has access: "Anyone"
4
Click "Deploy". Copy the "Web app URL" — that's the very link that ends in /exec.

Copy precisely the link that ends in /exec. A link with /dev won't work in Obsidian.

https://script.google.com/macros/s/AKfycbx…………/exec

3.4. Pasting the widget code into the template

Now we paste the widget code into the daily-note template. This is a ```dataviewjs block that draws a card with the week and the calendar. The most important thing — in the first line replace the link in quotes with yours from step 3.3:

const GAS_URL = "PASTE_YOUR_LINK_ENDING_IN_exec_HERE";

This is what it looks like at the start of the widget code (you only replace the line with GAS_URL, don’t touch the rest):

```dataviewjs
const GAS_URL = "https://script.google.com/macros/s/AKfyc…………/exec";

const obsidian = require("obsidian");

const fileName = dv.current().file.name;
const m = fileName.match(/^(\d{2})-(\d{2})-(\d{4})$/);

if (!m) {
  dv.paragraph("⚠️ The note title must be in the DD-MM-YYYY format");
} else {
  // … then comes the long widget code: week, progress,
  //   the "Tasks" button, loading events and tasks from the calendar …
}
```

Done! Now in the note you see the tasks for today, overdue tasks are highlighted in red, tasks can be marked as done and new ones added. It’s especially convenient on the phone: opened the widget — and quickly jotted down your tasks.

Checking that it works. Open your link in the browser, adding ?debug=1 at the end. If you see JSON with a list of your tasks — the server is working correctly.

For those who don't want to figure it out for ages

Try my ready-made Obsidian template and start systematising your information today

Learn about the template

Step 4. Plans for the day and buttons

The next section of the template is plans for the day. Here I have button labels from the Buttons plugin:

`button-spaced-repetition` `button-sport` `button-morning-routine` `button-work-on-project` `button-litso`

Each such label is one button that I press during the day:

ButtonWhat it launches
🃏 Spaced repetitionRecall the material I’m learning
💪 WorkoutsToday’s exercises (change automatically by muscle group)
🙏 MorningMy morning ritual — meditation and concentration
📋 TaskOpen the projects and take a task from there (the main button!)
😌 FaceA personal skincare routine

These functions were added gradually. At first the note had only the morning ritual. Then I added spaced repetition, workouts and the rest — as needed, so as not to overload the brain. I advise you too to start with one or two buttons.


Step 5. How the buttons (Buttons) work — in simple words

The most frequent misunderstanding of beginners: “why is `button-sport` written in the note, but a beautiful button appears?” Let’s figure it out.

The logic is divided into two parts: where the button is described and where it’s shown.

1. The "MOC - Buttons" note
Here lie the full descriptions of all the buttons (code + the ^button-… label). This is the "warehouse" of buttons.
2. The daily note
Here there's only the short label `button-sport` — and the plugin substitutes the button from the "warehouse."

Why so complicated? So as not to copy the long button code into every note. Described it once — insert it with a short label as many times as you like.

What a button description looks like

All my buttons are stored in one note MOC - Buttons (or just “Buttons”). Here’s an example of one button’s description:

```button
name 💪 Workouts
type command
action QuickAdd: Open workouts
color black
class btn-inline
```
^button-sport

Let’s go through it line by line:

nameWhat's written on the button (can be with emoji).
type commandThe "command" type — the button launches an Obsidian command.
actionExactly which command to launch (here — a choice from QuickAdd).
color / classThe appearance. btn-inline — a compact inline button.
^button-sport⭐ The main thing: the label (block id). This is the button's "address."

What ^button-... and the label in the text are

The line ^button-sport right under the code block is a block id (a block anchor) in Obsidian. It gives the button a unique address.

After this, in any note it’s enough to write the label as inline code (in single backticks):

`button-sport`

The Buttons plugin sees this label, finds the needed description by the address ^button-sport and draws a working button in its place. You can put several labels in a row in one line — you’ll get a row of buttons.

The rule is simple:

^button-name (with the “caret”) — this is where the button is described. `button-name` (in quotes) — this is where the button is shown. The name after button- must match.

How to create your own button from scratch

1
In the "MOC - Buttons" note, copy any ready ```button … ``` block.
2
Change the name (the label) and the action (which command to launch).
3
Below, set a unique label, for example ^button-water.
4
In the needed note write `button-water` — and the button will appear.

Step 6. The main “Task” button: connecting the diary and projects

The ”📋 Task” button is one of the main ones. Its label is button-work-on-project, and it launches the project-to-daily script via QuickAdd.

First, about where the tasks come from. All my projects are in the MOC - Projects note (the 1. Projects folder by the PARA structure). This is a kanban board on the Kanban plugin: just task cards that are dragged from status to status.

💡 Ideas
🔄 In progress
Done

The “Task” button does two smart things:

  1. Pulls tasks from the projects into the diary. A window with your kanban board opens, you tick the needed cards and click “Add” — they appear in the “My plans for the day” section.
  2. Moves tasks across the board on completion. When you mark a task as done (- [x]) right in the diary, it automatically moves to the next column of the kanban (e.g. from “In progress” to “Done”) and is removed from the note.

There's also a "turbo mode": if you turn on the "🏁 To the last" toggle (or add the 🏁 icon to the task), on completion it'll go straight to the very last column of the board.

What to replace in the script for yourself

The script is in my 0. Files/4. Templates/Scripts/project-to-daily_01.js. If you take it for yourself, at the very top there are two lines that need to be configured for your system:

const MOC_PATH = "1. Projects/MOC - Projects.md"; // ← the path to YOUR kanban board
const LAST_COLUMN_MARKER = "🏁";                   // ← the "straight to the last column" icon
  • MOC_PATH — specify the path to your main kanban board with projects (what it’s called and which folder it’s in).
  • LAST_COLUMN_MARKER — the marker icon for “turbo mode.” You can leave 🏁 or put any of your own.

Another important point: the script looks for where to insert tasks by the heading # Мои планы на день (My plans for the day). This heading must be in your daily note — otherwise tasks will be added at the end.

For those who don't want to figure it out for ages

Try my ready-made Obsidian template and start systematising your information today

Learn about the template

Step 7. Automatically carrying over uncompleted tasks

In the template, besides the buttons, there’s one more piece of code — it carries over uncompleted tasks from the previous day to today. It works on the Templater plugin: when a new note is created, the code fires once and turns into an ordinary task list.

The logic is simple and very convenient:

📄 Yesterday's note
find the last note before the current day
🔍 We look for "- [ ]"
gather all the unclosed tasks
📥 Today
insert them into the new note

Here’s this code from the template (it’s pasted as is, nothing needs to be changed — except, if you wish, the path to the daily-notes folder):

<%*
const moment = tp.obsidian.moment;
const fn = tp.file.title;
const date = moment(fn, 'DD-MM-YYYY');
 
// On Sundays we add a backup button
if (date.isValid() && date.day() === 0) {
  tR += '- [ ] `button-local-backup`\n';
}
 
const pathToDailyNotes = "2. Areas/Дневники/Ежедневные заметки"; // ← your path
const currentDate = moment(tp.file.title, 'DD-MM-YYYY');
const today = moment().startOf('day');
const referenceDate = currentDate.isAfter(today) ? today.clone().add(1, 'day') : currentDate;
 
function findLatestDailyNoteBeforeDate(beforeDate) {
  const folder = app.vault.getAbstractFileByPath(pathToDailyNotes);
  if (!folder || !folder.children) return null;
  const dailyFiles = folder.children
    .filter(file => {
      if (file.extension !== "md") return false;
      if (!file.name.match(/^\d{2}-\d{2}-\d{4}\.md$/)) return false;
      const fileDate = moment(file.name.replace('.md', ''), 'DD-MM-YYYY');
      return fileDate.isBefore(beforeDate) && fileDate.isSameOrBefore(today);
    })
    .sort((a, b) => {
      const dateA = moment(a.name.replace('.md', ''), 'DD-MM-YYYY');
      const dateB = moment(b.name.replace('.md', ''), 'DD-MM-YYYY');
      return dateB.valueOf() - dateA.valueOf();
    });
  return dailyFiles.length > 0 ? dailyFiles[0] : null;
}
 
if (!currentDate.isValid()) {
  tR += `❌ Couldn't determine the date from the file name. Format: DD-MM-YYYY.`;
} else {
  const yesterday = referenceDate.clone().subtract(1, 'day').format('DD-MM-YYYY');
  let targetFile = app.vault.getAbstractFileByPath(`${pathToDailyNotes}/${yesterday}.md`);
  let targetDate = yesterday;
  if (!targetFile) {
    targetFile = findLatestDailyNoteBeforeDate(referenceDate);
    if (targetFile) targetDate = targetFile.name.replace('.md', '');
  }
  if (!targetFile) {
    tR += `❌ No daily notes before ${referenceDate.format('DD-MM-YYYY')}.`;
  } else {
    const fileContent = await app.vault.read(targetFile);
    const tasks = fileContent.split("\n").filter(line => line.trim().startsWith("- [ ]"));
    if (tasks.length === 0) {
      tR += `✅ No uncompleted tasks for ${targetDate}.`;
    } else {
      tR += `## 🔁 Uncompleted tasks from ${targetDate} (the last note)\n\n`;
      tR += tasks.join("\n");
    }
  }
}
%>

If your diary had a gap of several days, the script will still find the last note before the current day and pull the tasks from it — nothing gets lost. You only need to change pathToDailyNotes if you have a different folder.


Step 8. Habit and food buttons (Meta Bind)

Next in the template come the habits. They work on the Meta Bind plugin, which lets you edit a note’s properties right from the text — no need to scroll up and open the properties panel.

`INPUT[toggle:Спорт]` 🏃‍♂️ Sport
`INPUT[toggle:Чтение]` 📖 Reading
`INPUT[toggle:Прогулка]` 🚶‍♂️ Walk

Here INPUT[toggle:Спорт] is a toggle linked to the Спорт property. You flipped the toggle in the body of the note — the checkbox is automatically set in the properties. And since the charts take the data precisely from the properties, your habit immediately gets into the statistics.

Food is set up the same way, only via text fields:

🥚 **Breakfast:** `INPUT[text:Завтрак]`
🍕 **Lunch:** `INPUT[text:Обед]`
🥧 **Dinner:** `INPUT[text:Ужин]`
🥨 **Snack:** `INPUT[text:Перекусы]`
 **Total:** `INPUT[text:Итого_ккал]`

`button-calories`

At the end there’s the button-calories button — ”🌮 Count calories”. This is my personal button that uses AI to estimate the calorie count from what I entered in the food fields.

It's not possible to fill everything in perfectly every day — and that's normal. But even a couple of filled-in days a week give clear dynamics on the habit and calorie charts.


Step 9. Thoughts — the most important section

And the most important thing — at the very bottom of the template is the “Thoughts” section. There’s no code or template here. It’s an absolutely empty space for reflection.

Here I write down during the day:

  • tasks for the future and ideas;
  • problems that worry me;
  • reflections and observations.

If I can't solve a task right now, I don't pressure myself. I write in "Thoughts" why I can't take it on. Often it turns out that the brain is stuck on another, external task — and the awareness of this itself removes the stupor.

These thoughts work for me further on too:

  1. On the home page. Tomorrow I’ll see yesterday’s thoughts in a special block on the home page (Homepage).
  2. In the MOC - Дневники note. There’s code there that gathers thoughts from all the daily notes and shows them by month — day by day. New tasks and atomic notes are born from them.
  3. From the phone. I have a widget set up that writes a thought straight into the right section of today’s note. Indispensable when an idea comes on the street.

The code: all thoughts from all daily notes by month

This block I paste into a separate note MOC - Дневники (it’s in my 2. Areas/Дневники). It goes through all the daily notes of the month, pulls the “Thoughts” section out of each (and also the properties Мысль_дня and Инсайт_дня, if they exist) and outputs them as a list. The ”← Previous / Next →” buttons flip through the months, and ”🔽 Newest first” changes the sorting.

What to replace for yourself

In the code there’s one line with the path to the daily-notes folder — put your path:

dv.pages('"2. Areas/Дневники/Ежедневные заметки"')

Also the block looks for the heading ## Мысли (Thoughts) and the property Дата (Date) in the notes. If your section is named differently (e.g. # Мысли) — it’ll still work (it looks for 1 to 3 hashes). The Дата property must be in the daily note’s properties in date format.

Copy the block whole — from the line with the word dataviewjs and to the closing backticks:

```dataviewjs
let currentDate = new Date();
let sortOrder = "desc";

const container = dv.el("div", "");

// Control panel
const controls = dv.el("div", "");
controls.style.cssText = `display: flex; align-items: center; gap: 8px; margin: 12px 0 20px 0; flex-wrap: wrap;`;

// Style for the buttons
function styleBtn(btn) {
  Object.assign(btn.style, {
    display: "inline-flex", "align-items": "center", "justify-content": "center",
    gap: "6px", padding: "8px 16px", "border-radius": "8px",
    border: "1px solid var(--background-modifier-border)",
    background: "var(--background-secondary)", color: "var(--text-normal)",
    cursor: "pointer", "font-size": "13px", "font-family": "inherit", "line-height": "1",
    transition: "background 0.15s, transform 0.1s, box-shadow 0.15s"
  });
  btn.onmouseenter = () => { btn.style.background = "var(--background-modifier-hover)"; btn.style.transform = "translateY(-1px)"; };
  btn.onmouseleave = () => { btn.style.background = "var(--background-secondary)"; btn.style.transform = "translateY(0)"; };
}

const spacer = dv.el("div", "");
spacer.style.cssText = "flex: 1;";

const prevBtn = dv.el("button", "⬅️ Previous");
const nextBtn = dv.el("button", "Next ➡️");
const sortBtn = dv.el("button", "🔽 Newest first");
styleBtn(prevBtn); styleBtn(nextBtn); styleBtn(sortBtn);

controls.appendChild(prevBtn);
controls.appendChild(nextBtn);
controls.appendChild(spacer);
controls.appendChild(sortBtn);
container.appendChild(controls);

const contentDiv = dv.el("div", "");
container.appendChild(contentDiv);

async function renderMonth(date) {
  contentDiv.innerHTML = "";
  const start = new Date(date.getFullYear(), date.getMonth(), 1);
  const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);

  const title = dv.el("h2", `${start.toLocaleDateString("en", { month: "long", year: "numeric" })}`);
  title.style.cssText = "margin: 0 0 12px 0; font-size: 1.1em;";
  contentDiv.appendChild(title);

  // ↓↓↓ REPLACE the path with your own daily-notes folder ↓↓↓
  const dailyNotes = dv.pages('"2. Areas/Дневники/Ежедневные заметки"')
    .where(p => {
      if (!p.Дата) return false;
      const d = new Date(p.Дата);
      return d >= start && d <= end;
    })
    .sort(p => p.Дата, sortOrder);

  for (const note of dailyNotes) {
    const content = await dv.io.load(note.file.path);
    const match = content.match(/#{1,3}\s*Мысли\s*\n([\s\S]*?)(?=\n#{1,3}\s|$)/i);
    const thoughtOfDay = note["Мысль_дня"];
    const insightOfDay = note["Инсайт_дня"];

    if (!match && !thoughtOfDay && !insightOfDay) continue;

    const block = dv.el("div", "");
    block.style.cssText = "border-bottom: 1px solid var(--background-modifier-border); padding: 8px 0;";

    block.appendChild(dv.el("h4", note.file.name));
    if (thoughtOfDay) block.appendChild(dv.el("p", `💭 Thought of the day: ${thoughtOfDay}`));
    if (insightOfDay) block.appendChild(dv.el("p", `💡 Insight: ${insightOfDay}`));
    if (match) block.appendChild(dv.el("p", `📝 Thoughts:\n${match[1].trim()}`));

    contentDiv.appendChild(block);
  }
}

prevBtn.onclick = () => { currentDate.setMonth(currentDate.getMonth() - 1); renderMonth(currentDate); };
nextBtn.onclick = () => { currentDate.setMonth(currentDate.getMonth() + 1); renderMonth(currentDate); };
sortBtn.onclick = () => {
  if (sortOrder === "desc") { sortOrder = "asc"; sortBtn.textContent = "🔼 Oldest first"; }
  else { sortOrder = "desc"; sortBtn.textContent = "🔽 Newest first"; }
  renderMonth(currentDate);
};

await renderMonth(currentDate);
```
📷 Screenshot: the MOC - Дневники note with a list of thoughts by month and toggle buttons
(insert an image here)

The home page: habits and thoughts

Every morning Obsidian opens for me not on an empty spot, but on a home page (the MOC - HOME note, the Homepage plugin). On it I immediately see the two most important things: the habits chart for the current month and yesterday’s thoughts. So the day starts with a short “overview of myself.”

This block consists of two tabs — “Habits” and “Thoughts”. The “Habits” tab builds a completion chart for the month (it takes the data from the daily notes’ properties). The “Thoughts” tab shows the “Thoughts” section from yesterday’s note.

What to replace for yourself

At the start of the code there’s one line with the path to the daily-notes folder — specify your path:

const HABITS_FOLDER = "2. Areas/Дневники/Ежедневные заметки";

And in the habits array, list your habits — the names must exactly match the property names in the template (Спорт, Чтение, Прогулка, etc.).

For the habits chart you need the Obsidian Charts plugin (it provides the window.renderChart function). Install it the same way as the rest: Browse → "Charts" → Install and enable.

Copy the block whole — from the line with the word dataviewjs and to the closing backticks:

```dataviewjs
// ⚙️ SETTINGS — replace the paths and the habit list for yourself
const { MarkdownRenderer } = require('obsidian');
const HABITS_FOLDER = "2. Areas/Дневники/Ежедневные заметки"; // ← your daily-notes folder
const habits = [
  { name: "Sport",    key: "Спорт" },
  { name: "Reading",  key: "Чтение" },
  { name: "Walk",     key: "Прогулка" }
];

// 📅 Current month
const now       = new Date();
const year      = now.getFullYear();
const month     = now.getMonth() + 1;
const pad       = n => String(n).padStart(2, "0");
const monthStr  = pad(month);
const totalDays = new Date(year, month, 0).getDate();
const MONTH_NAMES = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const monthName = MONTH_NAMES[month - 1];

// 📅 Yesterday's date (for the "Thoughts" section)
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
const yFileName = `${pad(yesterday.getDate())}-${pad(yesterday.getMonth() + 1)}-${yesterday.getFullYear()}`;
const yFilePath = `${HABITS_FOLDER}/${yFileName}.md`;

// 🛠️ Helpers
const asBool = v => v === true || v === "true" || v === 1;
function gradientColor(pct) {
  let r, g, b = 0;
  if (pct < 50) { r = 255; g = Math.round(255 * pct / 50); }
  else          { g = 255; r = Math.round(255 - 255 * (pct - 50) / 50); }
  return `rgb(${r},${g},${b})`;
}

// 🚀 Render
let viewMode = "habits";

async function render() {
  this.container.innerHTML = "";

  const tabs = this.container.createEl("div");
  tabs.style.cssText = "display:flex; gap:8px; margin-bottom:14px; flex-wrap:wrap;";
  const makeTab = (label, mode) => {
    const active = viewMode === mode;
    const btn = tabs.createEl("button", { text: label });
    btn.style.cssText = "padding:6px 16px; border-radius:6px; cursor:pointer; border:1px solid var(--interactive-accent); " +
      (active ? "background:var(--interactive-accent); color:var(--text-on-accent); font-weight:600;"
              : "background:transparent; color:var(--text-normal);");
    btn.addEventListener("click", () => { if (viewMode !== mode) { viewMode = mode; render.call(this); } });
  };
  makeTab("Habits",   "habits");
  makeTab("Thoughts", "thoughts");

  const box = this.container.createEl("div");

  // ── Habits ──────────────────────────────────────────────
  if (viewMode === "habits") {
    let pages = dv.pages(`"${HABITS_FOLDER}"`).where(p => {
      if (p.date) { const d = dv.date(p.date); return d && d.year === year && d.month === month; }
      const n = p.file.name;
      return n.includes(`-${monthStr}-${year}`) || n.includes(`${year}-${monthStr}-`);
    });
    if (pages.length === 0) { box.createEl("p", { text: `No data for ${monthStr}.${year}.` }); return; }

    const counts   = habits.map(h => pages.filter(p => asBool(p[h.key])).length);
    const percents = counts.map(c => Math.round((c / totalDays) * 100));
    window.renderChart({
      type: "bar",
      data: {
        labels: habits.map(h => h.name),
        datasets: [{
          label: `Completion for ${monthStr}.${year} (of ${totalDays} days)`,
          data: percents,
          backgroundColor: percents.map(p => gradientColor(p)),
          borderRadius: 4,
        }]
      },
      options: {
        indexAxis: "y",
        scales: {
          x: { min: 0, max: 100, ticks: { callback: v => v + "%" }, title: { display: true, text: "Percent" } },
          y: { title: { display: true, text: "Habits" } }
        },
        plugins: {
          legend: { display: false },
          tooltip: { callbacks: { label: ctx => `${ctx.raw}% (${counts[ctx.dataIndex]} of ${totalDays} days)` } },
          title: { display: true, text: `Habits — ${monthName} ${year}` }
        }
      }
    }, box);

  // ── Thoughts (from yesterday's note) ─────────────────────
  } else if (viewMode === "thoughts") {
    const tfile = app.vault.getAbstractFileByPath(yFilePath);
    box.createEl("div", { attr: { style: "font-weight:600; margin-bottom:10px; color:var(--text-muted);" } })
       .setText(`Thoughts — yesterday (${yFileName})`);
    if (!tfile) { box.createEl("p", { text: `Note not found: ${yFileName}`, attr: { style: "color:var(--text-muted);" } }); return; }

    const content = await app.vault.read(tfile);
    const match = content.match(/^#{1,6}\s*Мысли\s*$/m);
    if (!match) {
      box.createEl("p", { text: "The 'Thoughts' section wasn't found in the note.", attr: { style: "color:var(--text-muted);" } });
    } else {
      const startIdx    = content.indexOf(match[0]) + match[0].length;
      const afterText   = content.slice(startIdx);
      const nextHeading = afterText.match(/\n#{1,6}\s/);
      const thoughtsRaw = nextHeading ? afterText.slice(0, nextHeading.index).trim() : afterText.trim();
      if (thoughtsRaw) {
        const card = box.createEl("div", { attr: { style: "background:var(--background-secondary); border-left:3px solid var(--interactive-accent); border-radius:6px; padding:12px 14px; line-height:1.75; max-height:320px; overflow-y:auto;" } });
        await MarkdownRenderer.render(app, thoughtsRaw, card, yFilePath, this.component);
      } else {
        box.createEl("p", { text: "The 'Thoughts' section is empty.", attr: { style: "color:var(--text-muted);" } });
      }
    }
    const linkWrap = box.createEl("div", { attr: { style: "margin-top:10px;" } });
    linkWrap.createEl("a", {
      text: `Open ${yFileName} — Thoughts`, cls: "internal-link",
      attr: { href: `${yFileName}#Мысли`, "data-href": `${yFileName}#Мысли`, style: "font-size:0.85em; color:var(--interactive-accent); text-decoration:none;" }
    });
  }
}
render.call(this);
```
📷 Screenshot: the home page with the "Habits" and "Thoughts" tabs
(insert an image here)

For those who don't want to figure it out for ages

Try my ready-made Obsidian template and start systematising your information today

Learn about the template

Paper diaries are in the system too

I haven’t given up paper completely — sometimes it’s pleasant to write by hand. In my system there’s a button for this — ”📸 Photo to text” (the label button-image-2-text, the script image-to-text).

The logic is simple: I photograph a page of the paper notebook → the button recognises the handwritten text using AI → I can copy it into the daily note or create a new note from it.

📓 A paper sheet
📸 Photo to text (AI)
📝 Text in a note

And I also love drawing diagrams by hand — and any such drawing is easily inserted right into a note (via the Excalidraw plugin or the same drawing button). So keeping a diary in Obsidian is not limited in functionality: paper, drawings, voice, photos — everything flows into one place.


The takeaway

That’s the whole system. It looks large-scale, but it’s assembled from simple building blocks:

1.Plugins: Dataview, Meta Bind, Homepage, Templater, Buttons, QuickAdd.
2.The built-in "Daily notes" + a template in the Areas folder (PARA).
3.Properties (YAML) for habits and food.
4.The Google Calendar widget via scripts.google.com.
5.Buttons and a script connecting the diary with the projects.
6.Auto-carryover of tasks, habits, food and the thoughts section.

The main advice: don't implement everything at once. Start with the enabled daily notes and one morning-ritual button. When that becomes a habit — add the task carryover, then the calendar, then the projects. The system should unload the brain, not load it.

In the next materials I’ll show how I made myself a plugin for reading books (I highlight fragments and create notes from them) and how I clarify information with AI right in the process of reading.

For those who don't want to figure it out for ages

Try my ready-made Obsidian template and start systematising your information today

Learn about the template

© 2026 Elton Labs. All rights reserved.