Utku Aytaç18 downloadsRender a filtered, sorted stream of notes from a code block.
An Obsidian plugin that renders a filtered, sorted stream of notes wherever you
drop a stream code block.
Journal-shaped, but not journal-only: the filter decides what the stream is.
folder: Journal
tags: [book]
date-field: date
sort: date desc
group: day
display: preview
limit: 50
In Obsidian, open Settings → Community plugins, turn off Restricted Mode if
it is on, then Browse and search for Simple Streams. Install it, enable
it, and add a stream block to any note.
main.js, manifest.json and styles.css from the
latest release,
or build them yourself (see Development).<your vault>/.obsidian/plugins/simple-streams/.Each release asset carries a signed provenance attestation, so a downloaded build can be tied back to this repository:
gh attestation verify main.js --repo ukaytac/obsidian-simple-streams
Requires Obsidian 1.5.7 or newer. Works on desktop and mobile.
A query that can select from the vault has to be able to see the vault, so Simple Streams enumerates it — this is worth stating plainly rather than leaving you to find it in a permissions list.
app.vault.getMarkdownFiles(), one call
site: src/obsidian/adapter.ts. Markdown only —
not getFiles(), so attachments and non-note files are never enumerated.
What it takes from each is its path, name, tags, frontmatter and timestamps,
all of which are already in Obsidian's metadata cache. A folder: narrows
what a stream shows; the list it filters is still the whole vault.vault.cachedRead, and only for
the items a stream actually renders — bounded by the query's limit and, for
a long stream, by how far you have scrolled.fetch, no requestUrl, and
no dependency that makes one. Nothing about your vault leaves your machine.None of that is a claim you have to take on trust. The test suite and the type
check run in CI on every push, npm run check:floor proves each release against
the typings for the Obsidian version the manifest promises, and the assets carry
the attestation described under Installing.
| Field | Type | Default | Meaning |
|---|---|---|---|
folder |
text or list | whole vault | Path prefix, subfolders included |
tags |
text or list | — | All listed tags must be present |
tags-any |
text or list | — | At least one of the listed tags |
exclude-folder |
text or list | — | Drop notes under these paths |
exclude-tags |
text or list | — | Drop notes carrying any of these tags |
title |
text or /regex/ |
— | Matches the note's file name |
where |
map | — | Frontmatter conditions |
date-field |
text | file.ctime |
Which field is "the date" |
from, to |
date | — | Inclusive date bounds |
sort |
text or list | file.ctime desc |
"<field> <asc|desc>", direction defaults to asc |
group |
day|month|year|none |
none |
Date headers, using date-field |
display |
full|preview|title |
preview |
How much of the body to show |
preview-length |
number | 200 |
Character budget for previews |
limit |
number | 50 |
Maximum items |
Fields addressable in sort and where: any frontmatter key by name, plus
file.ctime, file.mtime, file.name and file.path.
Two things worth knowing about date-field, because they surprise people:
group reads date-field, not your sort field. If you sort by a
frontmatter date but leave date-field at its default, the headers say
file-creation dates and the stream is reordered to match them — your declared
sort survives only inside each group. Set date-field to the same field you
sort by, as the example above does.from and to also read date-field, and so does the date shown beside
each item.Tags match their descendants: tags: project also matches a note tagged
project/streams.
Folder paths, tag names, title text and where equality are all
case-insensitive. A title regex is not — write /weekly/i if you want it
to be.
A tag written with its hash must be quoted — tags: ["#book"] — because YAML
reads a bare # as a comment. Writing the tag without the hash needs no quotes.
where conditions: field: value (equality), field: [a, b] (any of),
field: exists / field: missing, and comparisons —
field: ">3", ">=3", "<3", "<=3", "!=done".
Comparisons must be quoted. Unquoted, YAML reads > and ! as its own
syntax and your condition becomes something else entirely; Simple Streams
rejects the result with an error rather than showing you an empty stream. A
field with no value matches only missing.
Equality looks inside a frontmatter list too: where: {tags: book} matches a
note whose tags are [Book, Read]. Numbers compare as numbers and booleans
as booleans.
Dates accept YYYY-MM-DD, today, yesterday, and signed offsets like -30d,
-2w, -6m, +1y. The sign is required — a bare 30d is an error rather than
a guess at which direction you meant. Month and year offsets clamp to the end of
the target month, so one month before 31 March is 28 February in a common
year and 29 February in a leap year.
Nothing stops a note from holding as many blocks as it needs. These are all
reading the same two folders — a Travels/ folder whose notes carry start
and end dates, and a Trip Notes/ folder of short notes tagged to a trip.
Only the query differs.
where is what makes a stream specific: a rating, a budget, a date in the
future, a field that is simply missing.
And what that page is, underneath: plain code blocks in a Markdown note.
npm install
npm test # engine, parser and one budget test
npm run dev # watch build
npm run build # type-check and bundle
main.js is a build artifact and is not in the repository — npm run build
writes it at the root, next to the manifest.json and styles.css a user
installs alongside it.
To try a change in a real vault, point a plugin folder at the build rather than copying after every edit:
VAULT=~/path/to/your/vault
mkdir -p "$VAULT/.obsidian/plugins/simple-streams"
ln -sf "$PWD"/{main.js,manifest.json,styles.css} \
"$VAULT/.obsidian/plugins/simple-streams/"
npm run dev
npm run dev writes main.js at the repo root on every save, and the symlinks
mean Obsidian sees it immediately — reload with Reload app without saving
from the command palette. Use a scratch vault: a stream is read-only, but a
plugin under development is still a plugin under development.
tests/view/ mounts the view in jsdom, which has one window and one rendering
mode. What that leaves over — popout windows, Live Preview, mobile — is in
docs/manual-testing.md, along with what each run of
it found.
Design: docs/superpowers/specs/2026-09-04-simple-streams-design.md Plan: docs/superpowers/plans/2026-09-04-simple-streams.md Publishing: docs/publishing.md
MIT — see LICENSE.