Visualize task completion as segmented progress bars in Logseq DB graphs.
Track progress derived from configurable properties (status, state, or any custom enum) on blocks with specified tags (#Task, #Project, etc.). Supports manual placement, query integration, and real-time updates.
DB graphs only — file-based graphs are not supported.
-
Clone the repo and build:
cd logseq-progressbar pnpm install pnpm buildRequires Node 24 (
.nvmrc) and pnpm. -
In Logseq, go to Settings → Advanced → Enable Developer mode
-
Open Plugins (three-dot menu → Plugins)
-
Click the three-dot menu in the Plugins panel → Load unpacked plugin
-
Select the
logseq-progressbardirectory
pnpm dev # Vite dev server at http://localhost:8296 (HMR, plugin auto-reload)
pnpm watch # Rebuild dist/ on change (no server)
pnpm test # Unit tests + feature × settings matrix, with the coverage gate
pnpm typecheck # Strict TypeScript check (includes the SDK-floor guard)
pnpm build # Production build
pnpm e2e # Live matrix in the Logseq web app at localhost:3001
pnpm e2e:desktop # Live matrix in a second instance of the desktop appWith pnpm dev running, load the plugin in Logseq via
Plugins → Load plugin from web url → http://localhost:8296/
(the dev server only answers CORS requests from localhost:3001).
Type /progress-bar in any block to insert a progress bar that tracks child blocks.
Insert the macro directly:
{{renderer :progress-bar}}The bar scans descendant blocks for configured tags and aggregates their statuses.
Override any global setting per-instance:
{{renderer :progress-bar, tags: Task|Bug, property: status, format: both}}{{renderer :progress-bar, tags: Project, property: state, complete: Completed, width: 300px}}Drop the renderer directly into a Logseq query block (the bar is auto-detected
from :logseq.property/query):
{{renderer :progress-bar}}Both query forms are supported:
-
Simple DSL — Logseq's query DSL. The
(task …)clause defines which statuses are tracked:(task "Todo" "Doing" "Done")
-
Custom datascript — full datalog as a
{:query […]}map or a bare[:find …]vector. The:findclause must pull blocks ((pull ?b [*])or an equivalent shape):{:query [:find (pull ?b [*]) :where [?b :logseq.property/status ?s] (or [?s :db/ident :logseq.property/status.todo] [?s :db/ident :logseq.property/status.doing] [?s :db/ident :logseq.property/status.done])]}
To attach a bar to a query block from elsewhere on the page, pass its UUID:
{{renderer :progress-bar, query: 69acb093-df21-438d-9bae-72decf188ed2}}Either way, results are grouped by the tracked property (status by default)
and counted in the plugin.
All parameters are optional. Global plugin settings are used as defaults.
| Param | Example | Description |
|---|---|---|
tags |
tags: Task|Bug |
Tags to track (pipe-separated) |
property |
property: state |
Property to read for progress |
complete |
complete: Done|Canceled |
Values that count as "complete" (pipe-separated) |
depth |
depth: 2 |
Scan depth (1, 2, ..., or all) |
strict |
strict: true |
Only count blocks with matching tags |
format |
format: both |
Label format: percent, fraction, or both |
width |
width: 200px |
Bar width: full, compact, or a CSS length / percentage (anything else falls back to full) |
query |
query: <uuid> |
Track a query block's results |
Open Plugins → Progress Bar → Settings to configure global defaults.
| Setting | Default | Description |
|---|---|---|
| Tags in Scope | Task, Checklist |
Comma-separated tag names to track |
| Property Mappings | {"Task": "status", "Checklist": "check"} |
JSON mapping each tag to its progress property. Checkbox-typed properties self-bucket (true → Done, false → Todo). |
| Complete Values | {"status": ["Done"]} |
JSON mapping each property to its "complete" values (string properties only — booleans bypass this). |
| Setting | Default | Description |
|---|---|---|
| Default Scan Depth | all |
How deep to scan (all or a number) |
| Strict Tag Filtering | false |
Exclude untagged blocks from the count |
| Display Format | percent |
percent (54%), fraction (7/13), or both |
| Bar Width | full |
full (100% width) or compact (inline) |
| Show on Empty | false |
Show an empty 0% bar when no items match |
| Debug logging | false |
Log scan and render details to the plugin console |
Hand-edited or stale values are coerced defensively: an invalid display
format falls back to percent, a depth of 0 or less means all, and a
JSON mapping that is not an object falls back to the default.
Override segment colors per status value (JSON):
{
"Done": { "light": "#22c55e", "dark": "#16a34a" },
"Doing": { "light": "#3b82f6", "dark": "#2563eb" }
}| Status | Light Mode | Dark Mode |
|---|---|---|
| Done | #4ade80 | #22c55e |
| Doing / Now | #60a5fa | #3b82f6 |
| In Review | #facc15 | #eab308 |
| Todo / Later | #e2e8f0 | #475569 |
| Backlog | #f1f5f9 | #334155 |
| Canceled | #fca5a5 | #ef4444 |
Colors adapt automatically to Logseq's dark/light theme.
- The renderer macro triggers on
{{renderer :progress-bar, ...}} - Global settings are merged with per-instance overrides
- Descendant blocks (or query results) are scanned at the configured depth
- Blocks are filtered by tags in scope
- The tracked property is read from each block; blocks with no value are skipped
- Status counts are aggregated and a completion percentage is calculated
- A segmented pill-shaped bar is rendered with completed segments on the left
- Hovering shows a tooltip with the full status breakdown
- The bar updates in real-time when task statuses change (
DB.onChanged)
- Project Alpha #Project
- Design UI #Task (status: Done)
- Implement API #Task (status: Doing)
- Write tests #Task (status: Todo)
- {{renderer :progress-bar}}Result: segmented bar showing 1/3 complete (33%).
- Q1 Goals
- Launch product #Project (state: Completed)
- Hire team #Project (state: Now)
- Raise funding #Project (state: Later)
- {{renderer :progress-bar, tags: Project, property: state, complete: Completed}}- Sprint 12 {{renderer :progress-bar, format: fraction, width: 200px}}
- Fix login bug #Task (status: Done)
- Add search #Task (status: Doing)
- Update docs #Task (status: Todo)The plugin ships #Checklist → check (a :checkbox property) as a built-in
default in settings. Tag children with #Checklist (or your own class that
declares a :checkbox property) — Logseq renders a native clickable checkbox
on the left of the block, and the progress bar updates in real time as you
toggle items.
- Trip prep {{renderer :progress-bar}}
- ☐ Book flights #Checklist
- ☑ Pack passport #Checklist
- ☑ Confirm hotel #Checklist
- ☐ Notify cat-sitter #ChecklistResult: 2/4 complete (50%). true lands in the Done bucket, false in
Todo. The plugin does not create the #Checklist class or check
property — author them yourself (or rename in settings to point at any class
you've already built, e.g. {"MyChecklist": "done"}).
A tagged block whose class declares a :checkbox property counts as
Todo even if you've never clicked the checkbox — Logseq renders ☐ by
default and the progress bar treats that as "not done yet" to match. Tick
it and the bar updates without you having to re-tag the block.
Checkbox and enum-status children can live under the same parent — they roll up into one bar.
src/
├── index.ts — Thin SDK adapter: the only module importing @logseq/libs
├── sdk.ts — SdkPort: the four SDK calls the pure modules need
├── progress.ts — resolveInstance → computeStats → renderTemplate pipeline
├── slots.ts — Registry of live renderer slots (+ generation counter)
├── settings.ts — Settings schema, readGlobalConfig (coercion), resolveConfig
├── parser.ts — Renderer macro argument parser
├── scanner.ts — Block scanning (children, page, query) through the port
├── query.ts — Query classifier, DSL→datalog, entity adapter, pull enricher
├── identCache.ts — Property ident / type / class-property cache
├── aggregator.ts — Status counting + completion calculation
├── colors.ts — Palette, segment ordering, colour validation
├── renderer.ts — HTML rendering, width validation, CSS styles
├── debug.ts — Verbose-log helper
├── *.test.ts — Vitest suites next to the module they cover
└── matrix.test.ts — Every feature × every settings permutation
scripts/e2e/ — Live matrices (web app and desktop app)Three layers, all repeatable:
| Layer | Command | What it proves |
|---|---|---|
| Unit + matrix | pnpm test |
Every pure module; 13 features × 144 settings permutations; 80 % coverage gate |
| Live, web app | pnpm e2e |
Seeds a page in the Logseq web app (localhost:3001), checks every scenario under 48 settings permutations in the host DOM, flips a status and a checkbox, toggles the theme, edits another page, and counts every SDK call the plugin makes (zero graph writes, reads stop after one debounced burst) |
| Live, desktop app | pnpm e2e:desktop |
The same matrix in a second instance of the installed desktop app on a throwaway graph; settings are applied in memory so the shared settings file is never written |
See the headers of scripts/e2e/progress-matrix.sh and desktop-matrix.sh
for prerequisites.
The plugin runs entirely inside your Logseq client. No data leaves the client — there are no external network calls, no telemetry, and no third-party services. The plugin's own page carries a Content-Security-Policy that only allows its bundled code.
Graph writes: none. The plugin never creates, edits or deletes blocks,
pages, tags or properties. It reads block properties and runs Datascript
queries against the loaded graph, and renders the bar into Logseq's plugin
UI slot (provideUI), which is DOM only. The live test suite (pnpm e2e)
counts every SDK call during a full run and fails if a single write occurs.
The only thing the plugin stores is its settings (tags, property mappings, colour overrides), which Logseq keeps in its standard plugin-settings file alongside other plugin settings.
MIT
