Lifelong Calendar is an Obsidian plugin for keeping a date-based timeline of your life inside your vault.
It is designed for people who want to record what they read, learned, built, visited, or experienced on specific dates, while keeping the actual data in Markdown files they control.
The plugin supports:
- timeline entries stored in your vault as Markdown
- internal Obsidian note links and external URLs
- a dedicated timeline view with search and filtering
- reminder emails for incomplete days
- grounded AI questions over timeline entries and linked notes
The plugin creates and manages dated timeline entries under a folder in your vault, which defaults to:
Lifelong Calendar/Entries
Each entry is stored as a normal Markdown file with frontmatter and note content. This means:
- your timeline data remains portable
- you can inspect and edit entries manually
- your data is not locked into a proprietary backend
The plugin also offers optional cloud-based reminder support. That part uses a small Cloudflare Worker and D1 database so reminders can still function when Obsidian is closed.
- Dedicated
Lifelong Calendarview inside Obsidian - Create and edit entries with date, title, category, links, and note
- Attach internal Obsidian links or external URLs
- Preview-first workflow from the detail pane
- Search entries by text
- Filter by year and category
- Open a random memory
- Sync today's completion state to a reminder backend
- Send reminder emails through Resend
- Ask grounded AI questions over entries and linked notes
Each entry is stored as one Markdown file. Filenames are generated from the date and a slug of the title.
Example:
---
lc_id: 123e4567-e89b-12d3-a456-426614174000
date: 2026-03-06
title: Read chapter 1
type: reading
links:
- "[[Book Notes]]"
- "https://example.com/article"
created_at: 2026-03-06T20:00:00.000Z
updated_at: 2026-03-06T20:00:00.000Z
---
Short note about the day.Important behavior:
- one file per entry
- unknown frontmatter fields are preserved
- links may be plain internal paths, wikilinks, Markdown links, or external URLs
- for reminder purposes, a day is considered complete if at least one timeline entry exists for that date
This repository contains the plugin source and build output.
To install manually into Obsidian:
- Run:
npm install
npm run build- Copy these files into your vault plugin folder:
manifest.jsonmain.jsstyles.css
Target path:
<your-vault>/.obsidian/plugins/lifelong-calendar/
- In Obsidian, open
Settings -> Community plugins. - Enable
Lifelong Calendar.
If you plan to submit this plugin to the official Obsidian Community Plugins directory, use this checklist:
- Keep these files in the root of your GitHub repository:
README.mdLICENSEmanifest.jsonversions.json
- Keep your source code in the repository.
- Do not commit
main.jsto the repository. Generate it only for releases. - Update
manifest.jsonwith the release version. - Create a GitHub release whose tag exactly matches the plugin version in
manifest.json. - Upload these release assets:
main.jsmanifest.jsonstyles.css
- Submit the repository to
community-plugins.jsoninobsidianmd/obsidian-releases.
Notes:
versions.jsononly needs updates whenminAppVersionchanges.- Obsidian Community Plugins currently does not support a custom listing icon through
manifest.json. - The icon in
assets/lifelong-calendar-icon.svgis for repository branding, README display, and other external use.
To generate the exact release assets locally, run:
powershell -ExecutionPolicy Bypass -File .\scripts\prepare-release.ps1This creates a versioned folder and zip file under release/ containing only:
main.jsmanifest.jsonstyles.css
Typical usage flow:
- Open the command palette.
- Run
Open Lifelong Calendar. - Create entries for today or any past date.
- Attach relevant notes or external links.
- Search or filter the timeline later.
You can create entries with:
Add Timeline EntryAdd Current Note to TimelineAdd External Link to Timeline
The entry modal requires:
- a valid
YYYY-MM-DDdate - a title
- at least one link or a note
The plugin currently provides these commands:
Open Lifelong CalendarAdd Timeline EntryAdd Current Note to TimelineAdd External Link to TimelineOpen Random MemoryAsk Lifelong CalendarSync Today's Reminder StatusSend Test Reminder Email
Entries folder: folder where timeline entry Markdown files are storedDefault categories: comma-separated category suggestions in the entry modalOpen internal links in new tab: open internal note links in a new leaf
Backend URL: deployed Cloudflare Worker URLBackend token: shared bearer token used by the pluginReminder email: address that receives reminder emailsReminder timezone: timezone used to determine the current dayReminder time: daily reminder time inHH:MMformatEnable reminders: enables completion sync
Reminder action buttons:
Save config: sends reminder settings to the backendSync today: sends today's completion state to the backendTest email: sends a test reminder email
Provider:OpenAI,Groq,Gemini,Ollama, orCustom OpenAI-compatibleAPI key: required for cloud providersBase URL: optional override, required for custom endpointsModel: model nameMax retrieved chunks: number of local sources sent to the model
The reminder system is optional.
When configured, the plugin syncs whether today is complete to a small backend. That backend can send reminder emails even when Obsidian is closed.
The backend stores:
- reminder email
- reminder time
- reminder timezone
- reminder completion state
- reminder delivery records
The backend does not store:
- your vault
- your Markdown files
- the full contents of your timeline entries for reminder purposes
- your AI chat answers
Reminder behavior:
- a day is complete if your timeline contains at least one entry for that date
- the plugin syncs today's completion automatically when relevant entry files change
- completion from Obsidian and web check-in is merged per source instead of blindly overwriting
- email links do not mutate reminder state on
GET - the email check-in flow requires an explicit confirmation
POST
This section is written for first-time Cloudflare users and assumes you want to set up reminders for yourself.
You need:
- a Cloudflare account
- a Resend account
- your Obsidian plugin already installed locally
For self-testing only:
- you may use
onboarding@resend.devas the sender address - your reminder recipient should be the same email address tied to your Resend account
For sending real reminders more broadly later:
- you should verify your own domain in Resend
Open a terminal in the backend folder:
cd D:\Opensource_repos\lifelong_calendar\backendLog in to Cloudflare through Wrangler:
npx wrangler@latest loginCreate the D1 database:
npx wrangler@latest d1 create lifelong-calendarCloudflare will print a database_id.
When Wrangler asks whether it should automatically add the binding snippet, choose n.
Reason:
- this repo already expects the binding name to be
DB - the Worker code uses
env.DB
Open backend/wrangler.jsonc and replace the placeholder values.
Keep:
"binding": "DB""database_name": "lifelong-calendar"
Replace:
"database_id": "replace-me"with your real D1 database ID"PUBLIC_BASE_URL": "https://replace-me.workers.dev"later, after the first deploy
Example structure:
Run:
npx wrangler@latest d1 execute lifelong-calendar --file .\schema.sql --remoteIf Wrangler asks whether to proceed even though the database may be temporarily unavailable, choose y.
This is expected when applying schema changes.
The Worker needs four secrets:
AUTH_TOKENCHECKIN_SECRETRESEND_API_KEYRESEND_FROM_EMAIL
What they are:
AUTH_TOKEN: secret shared between the plugin and your WorkerCHECKIN_SECRET: secret used to sign reminder email check-in linksRESEND_API_KEY: API key from your Resend accountRESEND_FROM_EMAIL: sender email address used by Resend
Use PowerShell:
"AUTH_TOKEN=" + [Convert]::ToBase64String((1..48 | ForEach-Object { Get-Random -Max 256 } | ForEach-Object { [byte]$_ }))
"CHECKIN_SECRET=" + [Convert]::ToBase64String((1..48 | ForEach-Object { Get-Random -Max 256 } | ForEach-Object { [byte]$_ }))Use the first value as AUTH_TOKEN and the second value as CHECKIN_SECRET.
They must be different.
In the Resend dashboard:
- Open
API Keys - Create an API key
- Copy the key value
For self-testing only, you can use:
onboarding@resend.dev
This is suitable if you only want to send to the same email address associated with your Resend account.
Run these commands one by one:
npx wrangler@latest secret put AUTH_TOKEN
npx wrangler@latest secret put CHECKIN_SECRET
npx wrangler@latest secret put RESEND_API_KEY
npx wrangler@latest secret put RESEND_FROM_EMAILPaste the correct values when Wrangler prompts you.
Deploy the Worker:
npx wrangler@latest deployCloudflare will print a Worker URL similar to:
https://lifelong-calendar-reminders.<your-subdomain>.workers.dev
Copy that URL.
Open backend/wrangler.jsonc again.
Replace:
"PUBLIC_BASE_URL": "https://replace-me.workers.dev"with the real deployed Worker URL.
Then deploy again:
npx wrangler@latest deployThis second deploy matters because reminder email links are built from PUBLIC_BASE_URL.
Open this in your browser:
<your-worker-url>/health
Expected result:
{"ok":true}In Obsidian, open the plugin settings and fill:
Backend URL: your deployed Worker URLBackend token: exactly the same value asAUTH_TOKENReminder email: your recipient emailReminder timezone: your timezone, for exampleAsia/KolkataReminder time: for example20:00- enable
Enable reminders
Then click:
Save configSync todayTest email
Verify the following:
- The test email arrives.
- Clicking the email link opens a confirmation page first.
- The day is only marked complete after clicking the confirmation button.
- Creating a timeline entry for today and syncing today marks the day complete from the plugin side as well.
If something fails:
Run:
npx wrangler@latest tailThen retry the failed action and inspect the logs.
database_idinbackend/wrangler.jsoncwas not replacedbindingwas changed fromDBto something elsePUBLIC_BASE_URLstill points to the placeholder URLBackend tokenin Obsidian does not matchAUTH_TOKENschema.sqlwas not applied to the remote D1 databaseRESEND_FROM_EMAILis invalid for your test mode or domain setup
The AI feature is optional.
Ask Lifelong Calendar searches your timeline entries and linked internal notes, then sends the top retrieved chunks to the configured model.
Provider:OpenAIAPI key: your OpenAI API keyBase URL: leave blankModel: a valid model such asgpt-4.1-mini
If you use the built-in Gemini provider:
Provider:GeminiAPI key: your Gemini API keyBase URL: leave blankModel: use a valid Gemini model
Important:
- do not set the Gemini
Base URLto the OpenAI-compatible Gemini endpoint when usingProvider = Gemini - the built-in Gemini provider in this plugin uses the native
generateContentAPI
If you specifically want the OpenAI-compatible Gemini endpoint:
- set
ProvidertoCustom OpenAI-compatible - set
Base URLto the Gemini OpenAI-compatible root URL - use a valid Gemini-compatible model name
Provider:OllamaBase URL:http://localhost:11434Model: a locally available Ollama model
- retrieval is currently lexical, not embedding-based
- citations are shown only when the model returns valid grounded citation IDs
- if a model returns malformed JSON or plain text, the answer may still display without citations
If Ask Lifelong Calendar says Failed to fetch, the most common reasons are:
- wrong provider selected
- wrong base URL
- local Ollama server not running
- invalid endpoint path
- network or firewall issue
For example:
- if
Provider = Gemini, leaveBase URLblank - if using a custom OpenAI-compatible endpoint, set
Base URLto the API root, not/chat/completions
Your actual timeline data stays in your vault as Markdown files.
That means:
- you can inspect entries manually
- you can back them up normally with your vault
- the plugin can rebuild its index from stored files
- reminder emails require your own Cloudflare and Resend setup
- AI chat requires your own provider configuration
- retrieval is lexical only for now
- reminder completeness is currently based on whether at least one entry exists for that date
Install dependencies:
npm installType-check the plugin:
npm run checkBuild the plugin:
npm run buildValidate the Worker syntax:
node --check backend/worker.mjs- src/: plugin source code
- main.js: built plugin bundle used by Obsidian
- styles.css: plugin styles
- manifest.json: Obsidian plugin manifest
- assets/lifelong-calendar-icon.svg: repository branding icon
- backend/worker.mjs: Cloudflare Worker reminder service
- backend/schema.sql: D1 schema
- backend/wrangler.jsonc: Cloudflare Worker configuration
The project currently includes:
- a working Obsidian timeline plugin
- Markdown-backed entry storage
- a deployable reminder backend
- AI retrieval and grounded-answer foundation
The remaining work is mostly product polish, deployment testing, and retrieval improvements.
{ "name": "lifelong-calendar-reminders", "main": "worker.mjs", "compatibility_date": "2026-03-06", "triggers": { "crons": ["*/30 * * * *"] }, "d1_databases": [ { "binding": "DB", "database_name": "lifelong-calendar", "database_id": "YOUR_REAL_DATABASE_ID" } ], "vars": { "PUBLIC_BASE_URL": "https://replace-me.workers.dev" } }