Periodic Notes

name

Periodic Notes

icon

cached

description

Creates recurring notes for time periods (weekly, monthly, quarterly, annually) automatically.

setting

Daily Note Template UUID

setting

Weekly Note Template UUID

setting

Quartely Note Template UUID

setting

Annual Note Template UUID

setting

Tags to exclude

instructions

linkPeriodic Notes plugin ✅

A simple plugin that helps you create and open regularly recurring notes — daily, weekly, monthly, quarterly, and yearly — using templates you already have.



This project was inspired by the [Obsidian Periodic Notes plugin](https://github.com/liamcain/obsidian-periodic-notes).


linkQuick start ✨

Install the plugin in your app.

Create one or more templates (for example, a "Weekly" template and a "Monthly" template).

In the plugin settings, tell the plugin which template to use for each frequency (Daily / Weekly / Monthly / Quarterly / Yearly) through the note UUID of the template.

Use the plugin commands (for example: **Open weekly note**) to open or create the note for the selected period.

That's it — the plugin will create a new note from the template if it doesn't already exist.


linkHow it helps you 🔧

Automatically create a new note for each day, week, month, quarter, or year from a template.

Open the note for the current period quickly using the plugin commands or Quick Open.

Use placeholders (wildcards) in the template name to create unique, readable note names automatically.


linkTemplate placeholders (wildcards) 💡

You can put these tokens in the **template name** to make note names change automatically:



{{YYYY}} → 4-digit year (example: **2025**)

{{YY}} → 2-digit year (example: **25**)

{{MMM} → full month name (example: **October**)

{{MM}} → 2-digit month (example: **06**)

{{M}} → month number without leading zero (example: **6**)

{{DD}}→ 2-digit day (example: **01**)

{{D}} → day without leading zero (example: **1**)

{{[Q]}} → quarter (example: **Q1**, **Q2**, **Q3**, **Q4**)



Examples:

Template name: `Weekly - {{YYYY}} {{MM}}-W{{D}}` → Note created: `Weekly - 2025 10-W6` (if appropriate for your naming)

Template name: `Monthly - {{YYYY}} {{MMM}}` → Note created: `Monthly - 2025 October`

Tip: Use `{{YYYY}}` and `{{MMM}}` together for clear, human-friendly names like `October 2025`.


linkUsing the plugin 📝

Open a note for the current period: use the command **Open daily note**, **Open weekly note**, **Open monthly note**, **Open quarterly note**, or **Open yearly note**.

If a note for that period doesn't exist, the plugin creates it from the corresponding template.

You can find and run these commands from your app's Quick Open feature.


linkTroubleshooting ⚠️

If the plugin doesn't create notes, check that you set a template for that period in the plugin settings.

If you have a very large number of notes and things feel slow, this is a known area for improvement (we're working on optimizing checks for existing notes).

If you don't know how to find a template's UUID or ID: open the note on the web app, and copy anything after `notes/` on the URL.

Alternatively, press `Ctrl + Shift + O` and copy the note identifier presented on the note details screen.


linkDeveloper notes (for contributors) 👩‍💻

Tests: Run `NODE_OPTIONS=--experimental-vm-modules npm test`.

For continuous testing during development:



-- bash
NODE_OPTIONS=--experimental-vm-modules npm run test -- --watch`


The project uses esbuild for bundling and Jest for tests.


linkRoadmap & ideas 📅

Improve performance for users with many notes.

Add a simple UI to set templates without leaving the plugin settings (work in progress).

More wildcards or customizable naming options.


If anything in this README is unclear, or you'd like an example tailored to your app's setup, open an issue on Github or leave a comment on the plugin page.

linkRoadmap & To-dos

linkCode block

(() => {
// lib/utils/noteTemplateMessages.js
function messageForNoteResult(result) {
if (result.ok) return null;
switch (result.reason) {
case "MISSING_TEMPLATE_UUID":
return "This note command is not configured. Please select a template in settings.";
case "TEMPLATE_NOT_FOUND":
return "The template note could not be found. It may have been deleted.";
case "TEMPLATE_LOOKUP_FAILED":
return "Could not access your notes. Please try again later.";
case "NOTE_LOOKUP_FAILED":
return "Something went wrong while searching for existing notes.";
case "NOTE_CREATION_FAILED":
return "The note could not be created. Please check your permissions.";
default:
return "An unknown error occurred.";
}
}
 
// lib/utils/noteTemplateService.js
function processNoteTitle(noteTitle) {
noteTitle = noteTitle.replace(/{{YYYY}}/g, (/* @__PURE__ */ new Date()).getFullYear().toString());
noteTitle = noteTitle.replace(/{{YY}}/g, (/* @__PURE__ */ new Date()).getFullYear().toString().slice(-2));
noteTitle = noteTitle.replace(/{{MMM}}/g, (/* @__PURE__ */ new Date()).toLocaleString("default", { month: "long" }));
noteTitle = noteTitle.replace(/{{MM}}/g, (/* @__PURE__ */ new Date()).getMonth().toString().padStart(2, "0"));
noteTitle = noteTitle.replace(/{{M}}/g, (/* @__PURE__ */ new Date()).getMonth().toString());
noteTitle = noteTitle.replace(/{{DD}}/g, (/* @__PURE__ */ new Date()).getDate().toString().padStart(2, "0"));
noteTitle = noteTitle.replace(/{{D}}/g, (/* @__PURE__ */ new Date()).getDate().toString());
noteTitle = noteTitle.replace(/{{[Q]}}/g, (/* @__PURE__ */ new Date()).getMonth() / 3 + 1).toString();
return noteTitle;
}
async function findIfNoteExists(app, noteTitle) {
const processedNoteTitle = processNoteTitle(noteTitle);
const existingNote = await app.findNote({ name: processedNoteTitle });
if (existingNote) {
console.log("Note with same name exists");
console.log(existingNote);
return existingNote.uuid;
}
}
async function createNoteFromTemplate(app, template, tagsToExclude) {
const processedNoteTitle = processNoteTitle(template.name);
const filteredTags = (template.tags || []).filter(
(tag) => !tagsToExclude.includes(tag)
);
const newNoteUuid = await app.createNote(
processedNoteTitle,
filteredTags
);
const content = await app.getNoteContent({ uuid: template.uuid });
await app.insertNoteContent({ uuid: newNoteUuid }, content);
return newNoteUuid;
}
async function openPeriodicNote(app, templateSettingKey, tagsToExclude = []) {
const template = await app.findNote({ uuid: app.settings[templateSettingKey] });
if (!template) {
return { ok: false, reason: "TEMPLATE_NOT_FOUND" };
}
try {
const existingNoteUuid = await findIfNoteExists(app, template.name);
if (existingNoteUuid) {
app.navigate(`https://www.amplenote.com/notes/${existingNoteUuid}`);
return { ok: true, action: "NAVIGATED_EXISTING" };
}
} catch (err) {
console.error("Error while looking for existing note:", err);
return { ok: false, reason: "NOTE_LOOKUP_FAILED", error: err };
}
const newNoteUuid = await createNoteFromTemplate(app, template, tagsToExclude);
app.navigate(`https://www.amplenote.com/notes/${newNoteUuid}`);
return { ok: true, action: "CREATED_NEW", uuid: newNoteUuid };
}
 
// lib/plugin.js
var plugin = {
constants: {
dailyNoteTemplate: "Daily Note Template UUID",
weeklyNoteTemplate: "Weekly Note Template UUID",
monthlyNoteTemplate: "Monthly Note Template UUID",
quartelyNoteTemplate: "Quarterly Note Template UUID",
yearlyNoteTemplate: "Yearly Note Template UUID",
tagsToExclude: "Tags To Exclude"
},
appOption: {
"Open daily note": async function(app) {
const templateUuid = app.settings[plugin.constants.dailyNoteTemplate];
if (!templateUuid) {
app.alert("Daily note template is not configured. Please select a template in plugin settings.");
return;
}
const result = await openPeriodicNote(app, plugin.constants.dailyNoteTemplate, app.settings[plugin.constants.tagsToExclude]);
const message = messageForNoteResult(result);
if (message) {
app.alert(message);
}
},
"Open weekly note": async function(app) {
const templateUuid = app.settings[plugin.constants.weeklyNoteTemplate];
if (!templateUuid) {
app.alert("Weekly note template is not configured. Please select a template in plugin settings.");
return;
}
const result = await openPeriodicNote(app, plugin.constants.weeklyNoteTemplate, app.settings[plugin.constants.tagsToExclude]);
const message = messageForNoteResult(result);
if (message) {
app.alert(message);
}
},
"Open monthly note": async function(app) {
const templateUuid = app.settings[plugin.constants.monthlyNoteTemplate];
if (!templateUuid) {
app.alert("Monthly note template is not configured. Please select a template in plugin settings.");
return;
}
const result = await openPeriodicNote(app, plugin.constants.monthlyNoteTemplate, app.settings[plugin.constants.tagsToExclude]);
const message = messageForNoteResult(result);
if (message) {
app.alert(message);
}
},
"Open quartely note": async function(app) {
const templateUuid = app.settings[plugin.constants.quartelyNoteTemplate];
if (!templateUuid) {
app.alert("Quartely note template is not configured. Please select a template in plugin settings.");
return;
}
const result = await openPeriodicNote(app, plugin.constants.quartelyNoteTemplate, app.settings[plugin.constants.tagsToExclude]);
const message = messageForNoteResult(result);
if (message) {
app.alert(message);
}
},
"Open yearly note": async function(app) {
const templateUuid = app.settings[plugin.constants.yearlyNoteTemplate];
if (!templateUuid) {
app.alert("Yearly note template is not configured. Please select a template in plugin settings.");
return;
}
const result = await openPeriodicNote(app, plugin.constants.yearlyNoteTemplate, app.settings[plugin.constants.tagsToExclude]);
const message = messageForNoteResult(result);
if (message) {
app.alert(message);
}
}
}
};
var plugin_default = plugin;
return plugin_default;
})()