Task Base
approvedby Tyler Teschon
Create and manage note-per-task records with RRULE recurrence, backed by a Bases task base. - This plugin has not been manually reviewed by Obsidian staff.
Task Base
An Obsidian plugin for a note-per-task system: capture a task through a form, pick a cadence from a builder instead of typing RFC 5545, and complete a task with the next due date computed for you.
It conforms to an existing schema rather than inventing one. A note is a task
because it carries type: task; a Bases file collects them from anywhere in
the vault.
A community plugin, not affiliated with or endorsed by Obsidian. "Bases" refers to Obsidian's own feature, which this plugin reads and writes but is not part of.
Screenshots
The pane's sections partition the open set — Later is whatever the other
sections did not catch, so nothing can be due-dated into invisibility. It is
collapsed by default so the pane opens on what is urgent.
Recurrence without typing RFC 5545. The preview shows the rule it will store,
what that means in English, and the next three dates it resolves to — here
30 September, 31 October, 30 November, because BYMONTHDAY=-1 means the last
day, not the 30th.

Capture on the left, edit on the right. Every field stays changeable after creation, and the asset field is a type-ahead over the notes in your vault rather than free text you can typo.
The same tasks in Obsidian's own Bases table. The plugin can create this base for you; it is optional, and the pane works without it.
How this differs from the other task plugins
No plugin in Obsidian's 7,000-plus community registry mentions RRULE. That
is the distinction: recurrence here is an RFC 5545 rule stored on the note —
FREQ=MONTHLY;INTERVAL=6;BYMONTHDAY=-1 — evaluated with the same library
calendar software uses, rather than a bespoke "every 6 months" string.
- Tasks manages checkboxes written inline in note bodies. Different unit of work entirely; the two do not overlap.
- TaskNotes is also note-per-task, built around a calendar, pomodoro and time tracking. If you want time tracking, use that.
This one is for things that come back on a schedule — maintenance, chores, services — where the note is the record, a Bases file is the view, and the recurrence rule is a standard you can read somewhere else.
The task note contract
---
done: false # checkbox
due: 2026-12-31 # date
created: 2026-08-31 # date
priority: low # low | medium | high
category: vehicle # home | yard | errands | vehicle | health
last done: 2026-06-19 # date — latest completion only
frequency: FREQ=MONTHLY;INTERVAL=6;BYMONTHDAY=-1 # RRULE, or empty
type: task # identity — this is what puts it in the base
asset: "[[Family Car]]" # optional
---
frequency is the only thing separating the two kinds of task. Set means
recurring; empty means one-time.
Commands
| Command | What it does |
|---|---|
| Create task | Form for name, category, priority, due, repeat rule, asset |
| Complete task | Branches on frequency — see below |
| Edit task | Change the name, due date, priority, category, repeat rule, asset or notes |
| Edit repeat rule | Opens the RRULE builder on a task |
| Set asset | Attaches, changes, or clears the asset on an existing task |
| Create asset note | Creates a type: asset note and opens it — no task needed |
| Recompute due date from repeat rule | Rolls due forward from last done |
| Open task base | Opens the base file, creating one if there isn't one |
| Open task list | The sidebar view |
The task pane carries its own toolbar — New task, refresh, and a button that opens the base file, creating one if you don't have a base yet. Its filters are generated from the Excluded folders setting, so a base made this way agrees with the pane by construction. It sticks to the top so the primary action stays reachable once the list scrolls. Right-click any row for Edit / Complete / Open.
Its sections — Overdue, Today, This week, Needs attention, Later — partition
the open set: Later is defined as everything not caught by the others, not
as a date window, so no open task can be missing from the pane while the footer
counts it. Later is collapsed by default, with its count on the header.
Commands act on the active note when it is a task, and offer a picker when it is not.
Notes
Both create forms end in a Notes box that becomes the note's body. On a phone that was the one field you could not see while typing into it: the form is taller than the screen long before the box is reached, so tapping it leaves the box at the bottom of the view with the on-screen keyboard over it.
So on a phone the box is not there. In its place is a one-line button carrying the opening of what you wrote, and tapping it opens an editor holding one field and its heading — the top of the screen, which is the half the keyboard leaves alone. Collapsing the box lifts every field above it back into view as well, which is the same complaint from the other end.
On a tablet or a desktop the inline box stays, with an expand button beside it opening that same editor for anything longer than a line.
The editor writes nothing to the vault: it edits the field, and the form still has to be submitted. It commits as you type rather than on Done, because Escape, a tap outside it, and the Android back gesture all dismiss a modal, and losing a paragraph to any of the three is not a trade worth making.
Edit task offers the same box on an existing note, filled from the note's
body. It is given the prose and not the completion log: the log is history —
one dated line per completion, written over months — and editing a paragraph
should not be able to take a service record with it. Everything from
## Service log down is carried across untouched, and the field says so when
there is one. Editing the log itself means opening the note.
The box also never rewrites what it did not touch. Save the form without changing the notes and the body is left byte-identical — the same rule the properties have always followed here, extended to the prose.
Renaming a task
Edit task carries a Name field, which renames the note. It goes through Obsidian's own rename, so every link pointing at the task is rewritten with it — a task renamed out from under the notes that reference it would be a worse outcome than the typo it fixed.
A name another note in the same folder already holds is refused, not quietly numbered the way a newly created note is. Creating a second "Mow lawn" is normal; renaming a task and silently getting "Mow lawn 1" is not what was asked for. Correcting only a name's capitalisation is not a collision with itself and goes through.
The rename runs last, after the properties and the notes are written, and reports on its own. A refused rename therefore costs you the rename — which the notice names — and not the edits made alongside it, which are already saved. The form stays open on it, since the field to correct is the one in front of you.
Assets
A task's asset property links the note for the thing being serviced —
asset: "[[Lawn Mower]]".
A note is an asset because it carries type: asset, the same rule this
plugin uses for tasks. A vault may also collect the same notes by folder — an
Inventory base filtering on file.inFolder("Inventory"), say. The two agree
as long as every asset note lives in that folder, and diverge if one is moved
out; matching on the property is what keeps the picker working either way.
The asset field is a type-ahead: click it and every asset drops down, typing narrows the list. A name with no matching note is still accepted — capturing a chore for something not yet inventoried is normal — but the field says so plainly rather than writing a dangling link in silence.
The field states that rule and the + beside it acts on it. Where the list came from used to be answerable only by reading this file: an empty dropdown explained neither why it was empty nor what would fill it. The field now says what makes a note an asset, and the button creates one without leaving the form — the new note appears in the list straight away, and its name drops into the field you were filling in.
New asset notes go to the New asset folder setting, and carry exactly two properties:
---
type: asset
created: 2026-09-03
---
That is the whole contract. type: asset is what puts the note in the picker;
created matches what tasks carry and is a real date, so it sorts. Nothing
else is written, because a lawn mower is described with fields no task plugin
can guess — model numbers, warranty dates, where it lives — and those are yours
to add.
The folder is only consulted when writing one. Assets are still found anywhere in the vault by their property, so moving a note out of that folder does not stop it being an asset.
Completing a task
- Recurring —
last done= today,duerecomputed,doneback tofalse. A recurring task is never left atdone: true; that is what makes it recur. - One-time —
last done= today,done=true. The note stays on disk and stays in the base. Nothing is ever deleted. - Unreadable rule — refuses to act and asks for the rule to be fixed.
Completing it as one-time would write
done: trueand retire a task that was meant to keep recurring.
Anything that is not a date — mileage, part numbers, what was actually done —
goes on a dated line under ## Service log in the body, never into last done.
How the next due date is computed
The policy is skip the rest of the period you just did it in, then take the schedule's next occurrence.
| Rule | Completed | Next due |
|---|---|---|
FREQ=WEEKLY;BYDAY=MO | Mon 2026-08-24 | 2026-08-31 |
FREQ=WEEKLY;BYDAY=SU | Mon 2026-08-17 | 2026-08-30 |
FREQ=WEEKLY;INTERVAL=2;BYDAY=MO | Mon 2026-08-24 | 2026-09-07 |
FREQ=MONTHLY;BYMONTHDAY=-1 | 2026-08-22 | 2026-09-30 |
FREQ=MONTHLY;INTERVAL=6;BYMONTHDAY=-1 | 2026-06-19 | 2026-12-31 |
FREQ=YEARLY | 2026-06-19 | 2027-06-19 |
Three cases, in order:
- No day-selection in the rule (
FREQ=YEARLY) — the day is implied by the anchor, so the roll is one period later. A yearly task done 19 June comes back the following 19 June, not on 1 January. - More than one occurrence per period (
FREQ=WEEKLY;BYDAY=MO,TH) — the remaining occurrences this period are real, so take the strict next one. - One occurrence per period — anchor the rule at the start of the current
period so nothing is clipped, then take the first occurrence after that
period ends.
INTERVALdoes the rest of the skipping itself.
Case 3 is why this is not a plain rrule.after(today). Anchoring at the
completion date makes INTERVAL step from there, so an oil change on
2026-06-19 under FREQ=MONTHLY;INTERVAL=6;BYMONTHDAY=-1 would roll to
2026-06-30 — eleven days out instead of six months.
Things this plugin will not do
- Delete a note. Sweeping finished one-time tasks is deliberately out of
scope. The sidebar surfaces recurring tasks stuck at
done: true, and unreadable repeat rules, and the generated base carries aSweepview of finished one-time tasks - but all three only ever report. - Write
.obsidian/types.json. The property-type registry is vault-wide; a stray write there breaks date sorting on every note at once. - Write to a Kanban board or any hand-maintained table. Frontmatter is the source of truth.
Known seam: the base filters are duplicated
Bases exposes no documented plugin API, so src/model/taskRepository.ts does
not query tasks/task base.base. It walks the vault and re-implements the
base's two filter clauses against the metadata cache:
type == "task" and !file.inFolder("Templates")
If the .base file's filters change, that file must change with them, or
the sidebar and the base will quietly disagree. Excluded folders is a
setting for exactly this reason.
src/model/assetRepository.ts is the same shape for assets, and matches on
type: asset rather than mirroring inventory.base's folder filter — see
Assets above for why.
Installing in another vault
A packaged Obsidian plugin is three files — main.js, manifest.json,
styles.css — in <vault>/.obsidian/plugins/task-base/. There is no installer
format beyond that.
From a release (any machine)
Pushing a tag that matches manifest.json's version builds and publishes those
three files as release assets:
git tag 0.2.2 && git push origin 0.2.2
Then, on the machine with the other vault:
gh release download 0.2.2 --repo tteschon/obsidian-task-base \
-D "<vault>/.obsidian/plugins/task-base"
Or download the three assets from the Releases page while signed in — the repo being private only means you must be authenticated, not that this stops working. Enable Task Base under Settings → Community plugins afterwards; if Obsidian is already running, reload it first so it rescans the plugins folder.
The release workflow refuses to publish when the tag disagrees with
manifest.json, because Obsidian matches a release to an installed plugin by
that version and a mismatch installs nowhere. npm version patch bumps
manifest.json and versions.json together, which keeps them honest.
On this machine
ln -s /path/to/repo <vault>/.obsidian/plugins/task-base # after npm run build
One build then serves every vault. Copying the three files by hand works too, and lets each vault run a different version.
A new vault needs configuring, not just the files
The plugin finds tasks by type: task anywhere in the vault, so the
defaults — which describe one particular vault — are usually wrong somewhere
else. Four settings matter:
- New task folder — where new notes land. Created if missing.
- New asset folder — where the + beside the asset field writes new asset notes. Created if missing, and matched to a folder that already exists under a different capitalisation rather than colliding with it.
- Excluded folders — any folder whose notes carry
type: taskfor an unrelated reason. This is the one that bites: a vault with Kanban cards or anothertype: taskconvention will otherwise list them in the pane's count and offer them in the Complete task picker, where choosing one writesdone: trueandlast doneinto a note that was never a task. - Task base — clear it, or point it at that vault's base. When the path does not resolve, the pane's "Open task base" button is simply hidden.
No .base file is required. The pane reads the metadata cache directly, so
every command and every section works in a vault with no base at all. The base
only adds Obsidian's own table view. If you want one, the pane's base button
creates it — that is the whole setup.
Property types need no setup either: Obsidian infers done as a checkbox and
due / last done as dates from the values the plugin writes. The plugin never
touches .obsidian/types.json.
created is a date, vault-wide
created used to hold a wikilink to the daily note — "[[2026-08-31]]" — which
made it text, so it could not be sorted, compared, or used in a formula.
It is now a bare YYYY-MM-DD, registered as date in .obsidian/types.json.
Worth knowing before touching it again: Obsidian property types are
vault-wide, not per-note-type. created was on 259 notes in three
incompatible formats — 194 daily notes carrying ["YYYY-MM-DD HH:MM"], 36
wikilinks, 23 bare dates — so registering the type meant normalising all of
them. Daily notes lost the time component in the process.
The plugin writes the bare form, and the vault's task template matches.
What the plugin reads, and why it reads the whole vault
To find tasks it enumerates the vault's markdown files and reads each one's
frontmatter, keeping those with type: task (and type: asset for the asset
picker). That is inherent to the design rather than incidental: a note is a task
because of a property, not because of where it sits, and Obsidian offers no API
to query notes by frontmatter property.
It reads frontmatter only — never the body of a note it was not asked to open — and the asset list is cached rather than recomputed per keystroke. It makes no network requests of any kind, collects no telemetry, and never deletes a note. Everything it writes goes through the vault API, inside the vault.
If you would rather it looked at less, Excluded folders narrows what it considers.
A hazard worth knowing
Do not name a method on the view class after a Workspace lifecycle verb.
View.open() is a real method Obsidian calls to open a view, but it is absent
from obsidian.d.ts — so defining private open(task: Task) on the view type
checks cleanly, overrides Obsidian's own method, and the pane then renders
blank: the view constructs, onload and onOpen never run, and nothing is
logged anywhere. It is named openNote for that reason.
render() catches its own failures and paints the error into the pane, so the
next render bug reports itself rather than showing an empty panel.
Layout
src/
main.ts commands, view registration, ribbon
settings.ts settings + tab
dates.ts all-day date helpers
recurrence.ts RRULE parse / describe / next-due / build
rruleCompat.ts CJS/ESM interop shim for the rrule package
model/frontmatter.ts pure text: note rendering, YAML normalisation
model/assetLink.ts pure text: asset wikilink <-> bare name
model/completion.ts pure: the branch completing a task takes
model/task.ts the field contract, read + write
model/asset.ts write an asset note
model/note.ts placing a new note: folder, casing, name collisions
model/taskRepository.ts find and bucket tasks
model/assetRepository.ts find asset notes
settingsData.ts pure: settings shape, defaults, migration
ui/ modals and the sidebar view
test/ node:test suites over the pure modules
model/frontmatter.ts holds everything that does not touch the Obsidian API,
which is what makes the test suite runnable outside the app — and those are the
functions most worth testing, since a regex that strays out of the frontmatter
would rewrite someone's note body.
Third-party code
main.js is a bundle: it contains rrule
(BSD-3-Clause). Its notice travels in the banner at the top of every built
main.js as well as in THIRD-PARTY-NOTICES.md.
Development
npm install
echo "$HOME/path/to/YourVault/.obsidian/plugins/task-base" > .vault-plugin-dir
npm run dev # watch build, writes straight into the vault plugin folder
npm test # node:test over recurrence, completion, frontmatter, asset links, settings
npm run lint # eslint
npm run build # typecheck + minified build to the repo root, for release
The dev build destination is per-machine and not committed. It comes from
.vault-plugin-dir (gitignored) or from a VAULT_PLUGIN_DIR environment
variable, which wins if both are set. npm run dev writes main.js,
manifest.json and styles.css there so an Obsidian reload picks the change
up, and exits with an error if neither is set rather than quietly building
somewhere you are not watching. npm run build ignores both and writes to the
repo root.
After the first build, enable Task Base in Settings → Community plugins.
Cutting a release
npm version patch && git push --follow-tags
npm version bumps manifest.json and versions.json together and creates the
tag; --follow-tags pushes the commit and the tag as one step. Bumping without
tagging breaks installs silently — the directory reads the manifest to learn
the current version and then downloads the release tagged with it, so a listing
that looks healthy fails for everyone. The Release drift workflow warns on
push and fails on its daily run if the two ever disagree.
Reload it after a rebuild with the Obsidian CLI:
obsidian plugin:reload id=task-base
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.