craigstfn120 downloadsNotion-style databases with board, calendar and gallery views, relations, rollups, charts and a slash command menu for your notes.
Bring the parts of Notion you actually miss into Obsidian: the / menu, databases,
table / board / gallery / list / calendar / timeline views, inline property editing,
sub-items, relations, rollups, and charts built from your own data.
Everything stays plain markdown. There is no lock-in, no sync service, and no
account. If you uninstall this plugin tomorrow, every note you made is still a
normal .md file you can open in any editor.
Notion stores your databases on Notion's servers, in a format only Notion understands. Obsidian stores everything as markdown files in a folder on your computer.
So this plugin does not invent a new storage format. It reuses what Obsidian already has:
| In Notion | Here |
|---|---|
| A database | A folder |
| A row / page in that database | A note inside that folder |
| A property on that row | A line of frontmatter at the top of that note |
| A view (board, calendar…) | A small code block you paste into any note |
That's the whole model. Once it clicks, everything else follows.
Here's a row. It is just a note:
---
status: In progress
priority: 3
due: 2026-03-14
tags:
- Work
- Urgent
---
Notes about this task go here, like any other Obsidian note.
That block between the --- lines is frontmatter. Obsidian already understands it;
this plugin gives it a Notion-style interface. When you tick a checkbox or drag a card
between board columns, the plugin edits those frontmatter lines. Nothing else.
Why this is worth knowing: you can always edit a row by hand, in any text editor, without the plugin. That is the escape hatch Notion never gave you.
Requires Obsidian 1.13.0 or later. The settings tab uses the declarative settings API, which is what puts your settings into Obsidian's own search.
Settings → Community plugins → Browse → search for Notion Suite → Install → Enable.
Updates arrive through Obsidian itself from then on; there is nothing else to set up.
If it doesn't appear in Browse, Obsidian is showing you a cached plugin list. Close and reopen the Browse dialog, or restart Obsidian, and search again.
BRAT installs a plugin straight from a GitHub repository. Useful if you want releases the moment they are cut rather than when the directory catches up.
https://github.com/navysum/notion-suite, choose Latest version,
and tick "enable after installing".You need Node.js.
git clone https://github.com/navysum/notion-suite
cd notion-suite
npm install
npm run build
That produces main.js. Copy main.js, manifest.json and styles.css into:
<your vault>/.obsidian/plugins/notion-suite/
Create that folder if it doesn't exist. (On macOS press Cmd+Shift+. in Finder
to reveal hidden folders; on Windows tick "Hidden items" in File Explorer's View
tab.) Then in Obsidian go to Settings → Community plugins, hit the reload icon,
and toggle Notion Suite on.
main.js is deliberately not committed to this repository — it is a build
artifact, published as a release asset. The community directory verifies that a
release was genuinely built from the committed source, and a stale checked-in
bundle is what that check exists to catch.
You should now see a database icon in the left ribbon.
Step 1. Make a new note. Call it anything — Dashboard works well.
Step 2. In the body of the note, type /database.
A menu appears as soon as you type the /. Pick Database — new.
Step 3. Choose a starter. There are nine, including Task tracker, Projects, Goals, Habit log, Reading list, Expenses and People/CRM. Pick Task tracker for now — it comes with a Status property already set up, which is what makes a board work.
Leave "Add a few example rows" ticked so you have something to look at.
Step 4. Press Create database.
Two things just happened:
Databases/Tasks by default), with three example notes in it.database: Tasks
view: board
group: status
Step 5. Switch the note out of edit mode (Cmd/Ctrl+E, or click away from the block).
You now have a working kanban board. Try these:
status: line has changed. That's the whole trick.Step 6. Add a second view of the same data. Type /database again, pick
Database — view, choose Tasks, and choose Calendar.
You now have two live views of the same folder, both editable, both staying in sync. That is the thing Notion does that plain markdown doesn't.
Type / anywhere in the editor. Type more letters to narrow it down, arrow keys to
move, Enter to insert, Esc to dismiss.
The / only triggers at the start of a line or after a space, so URLs (https://),
dates (2026/03/14) and phrases like and/or are left alone.
Basic blocks — Text, Heading 1/2/3, Bulleted list, Numbered list, To-do list, Toggle list, Toggle heading 1/2/3, Quote, Divider, Code, Table, Columns
Page structure — Table of contents, Breadcrumb, Page icon and cover
Callouts — Callout, Tip, Warning, Danger, Success, Question
Databases — Database (new), Database view, Add item, Chart from database, Widget
Inline — Date (today), Date (tomorrow), Link to page, Embed page, Equation, Highlight
You don't have to type the exact name. Every command has aliases, so /kanban finds the
database view, /graph finds the chart, /collapse finds the toggle, /checkbox
finds the to-do, and /banner finds the page icon and cover.
Want a different trigger character? Settings → Notion Suite → Trigger character.
You never have to write this by hand. /database → Database view opens a
dialog for every setting. On any rendered view, the toolbar has a ⚙ Settings
button that reopens it, pre-filled.
Switching view type is even quicker: click the view-type button in the toolbar (Table, Board, …) and pick another. That choice is saved into the note, so it sticks — it isn't just a preview.
Table — a spreadsheet. Click any cell to edit it. Click a column header to sort, hide it, or set a footer calculation (sum, average, percent checked…). Rows with sub-items get a twisty to fold them away.
Board — kanban. Drag cards between columns to change the grouping property. Empty columns still show, so you can drag into them. See Boards in depth.
Gallery — cards with cover images. Good for a reading list, recipes, or anything visual.
List — a compact line per row. If the database has a checkbox property, it becomes a tickable to-do list.
Calendar — a month grid. Hover a day and press + to create a row already dated to
that day. Arrows move between months.
Timeline — a Gantt-style chart. Each row becomes a bar between a start and an end date. Scale it by day, week or month.
A view is a fenced code block tagged notion-db. The only required line is database:.
```notion-db
database: Tasks
view: board
group: status
```
| Key | What it does | Example |
|---|---|---|
database |
Which database to show. Required. | database: Tasks |
view |
table, board, gallery, list, calendar or timeline. Defaults to table. |
view: board |
title |
A heading for this view. | title: This week |
group |
Board columns. Needs a select, status, multi-select or checkbox property. | group: status |
subgroup |
A second grouping that splits each board column. | subgroup: priority |
collapsed |
Board columns that start folded. | collapsed: [Done] |
limits |
Per-column card limits (WIP limits). | limits: {Doing: 3} |
limit_mode |
soft (default), ask or strict — what a limit does on a drop. |
limit_mode: ask |
buckets |
Bucket a date grouping into overdue / today / this week / later. | buckets: true |
date |
Which date property the calendar uses. | date: due |
start / end |
The properties bounding each timeline bar. | start: begins |
scale |
Timeline scale: day, week or month. |
scale: week |
cover |
Which property holds the card image (gallery/board). | cover: cover |
size |
Gallery card size: small, medium, large. |
size: large |
properties |
Which properties to show, in this order. | properties: [status, due] |
hide |
Properties to hide in this view only. | hide: [notes] |
calculate |
Table footer calculations, per property. | calculate: {hours: sum} |
filter |
Which rows to show. See below. | filter: [Status is not Done] |
sort |
Row order. | sort: due asc |
limit |
Show at most this many rows. | limit: 10 |
Two views of a database are useful. Fifteen are a filing problem, which is why Notion puts databases in a sidebar rather than only inside pages.
The sidebar. Click the database icon in the left ribbon, or run Notion: Show the databases sidebar. Every database in the vault is listed with a live row count. Click one to open it.
Full-page databases. A database opened from the sidebar fills a whole tab, with no host note around it — the same views, the same toolbar, the same editing, just not embedded in anything. Run Notion: Open a database to jump straight to one.
Use inline blocks when a view belongs to a note ("this project's tasks"), and the sidebar when the database is the thing you're working in.
Filters are written the way you'd say them out loud — in the settings dialog's Filter box, one per line. This is the one place you do type something, and it's deliberately English rather than syntax.
```notion-db
database: Tasks
view: table
filter:
- Status is not Done
- Due is not empty
- Priority >= 3
sort: due asc
```
Multiple filters are combined with and by default. For or, nest them under any:
```notion-db
database: Tasks
filter:
any:
- Status is Blocked
- Priority is Urgent
```
You can also write a group inline, and nest up to three levels deep:
filter:
- Priority >= 3
- (Status is Doing or Owner is me)
Operators you can use:
is · is not · contains · does not contain · starts with · ends with ·
is empty · is not empty · before · after · on or before · on or after ·
> · >= · < · <= · = · !=
Two useful tricks:
Due before today — the word today works anywhere a date does, and re-evaluates
every time the view renders.Name contains report — Name refers to the note's title, not a frontmatter key.Sorting: sort: due asc, sort: -priority (the minus means descending), or a list
for tie-breaking:
sort:
- priority desc
- due asc
| Type | Stored as | Editing |
|---|---|---|
text |
a string | click to type |
number |
a number | click to type; formats as plain, percent or currency |
select |
a string | click for a menu of options |
status |
a string | like select, but options are bucketed into To-do / In progress / Done |
multiselect |
a list | click to tick several |
date |
YYYY-MM-DD |
click for a date picker |
checkbox |
true / false |
click the box |
url / email / phone |
a string | rendered clickable, pencil icon to edit |
person |
a list of names | click to tick several |
files |
a path or URL | used as the cover image in gallery and board |
relation |
a list of note titles | click to link rows in another database |
rollup |
computed | read-only, see below |
formula |
computed | read-only, see below |
uniqueid |
a number, with an optional prefix | assigned once, never reused |
created / updated |
from the file itself | read-only |
This is the pair that makes a set of databases behave like one system, and it's worth setting up once slowly so you see how the two halves fit.
A relation links a row to rows in another database. A rollup reaches through that link and does maths on what it finds. Relations alone just give you clickable links; rollups are what turn them into numbers.
Worked example — Projects that total up their Tasks.
Say you have a Tasks database with an Hours number property and a Done
checkbox, and a Projects database. You want each project to show its total
hours and how far along it is.
Step 1 — add the relation. Open a view of Projects, click Properties →
New property…. Name it Tasks, set the type to Relation, and choose
Tasks as the related database. Save.
You now have a Tasks cell on every project. Click one and tick the tasks that belong to that project. Behind the scenes this writes a plain list of note titles into the project's frontmatter:
tasks:
- Design the homepage
- Build the homepage
Step 2 — add the rollup. Properties → New property… again. Name it
Total hours, type Rollup. Now three dropdowns appear, and they read as a
sentence:
Tasks — which link to followHours — what to grab off each related rowSum — how to squash those into one numberSave. Every project now shows the total hours of its tasks, and it updates the moment you change an hour count on any task.
Step 3 — try a different calculation. Add another rollup called Progress,
same relation, property Done, calculate Percent checked. That's a live
completion percentage per project, computed from the task notes themselves.
| Group | Functions |
|---|---|
| Show | Show original — lists the values rather than reducing them |
| Count | Count all (related rows), Count values, Count unique values, Count empty, Count not empty, Percent empty, Percent not empty |
| Numbers | Sum, Average, Median, Min, Max, Range |
| Dates | Earliest date, Latest date, Date range (days) |
| Checkboxes | Checked, Unchecked, Percent checked, Percent unchecked |
The same list powers table footer calculations — click a column header and pick one to get a total under the column.
Two distinctions that trip people up:
Count all vs Count values — Count all counts related rows. Count values counts the values gathered, which is larger when the property you're
rolling up is a multi-select. Rolling up a Tags multi-select across 3 rows
that carry 7 tags between them gives Count all: 3, Count values: 7.Percent checked divides by every gathered value,
including unset ones, so a task with no Done value drags the percentage down
rather than being ignored.{Total hours} * {Rate} works and gives you a live project cost.[[Wikilinks]],
[[Links|with aliases]] and bare text all work. If two notes in the target
database share a title, the first one found wins.Reference other properties in {braces} and use + - * / ( ):
{Current} / {Target}
({Revenue} - {Cost}) / {Revenue}
{Hours} * 85
This is deliberately a small language. It does arithmetic and nothing else — it cannot
run code, which is why it's safe to have it evaluate automatically on every render.
Checkboxes count as 1 and 0, and a reference to a property that doesn't exist counts
as 0. Division by zero gives an empty cell rather than an error.
A row can be nested under another row. Add a property to hold the parent's title (Properties → New property…, type Text or Relation), and turn on Use as the parent link in that same dialog. Rows that name a parent appear indented underneath it, with a twisty to fold them away.
Because the link is an ordinary property holding a title, the hierarchy is visible and editable in the note itself — not hidden in plugin state.
Deleting a parent asks what to do with anything nested under it: take them too, or leave them (they become top-level rows). Dismissing that dialog cancels the delete outright.
Add a property of type Unique ID and every row gets a number that is assigned
once and never reused, optionally with a prefix — TASK-1, TASK-2. Deleting a
row does not free its number, which is the point: an ID you can quote in a
conversation has to keep meaning the same thing.
Already have rows? Run Notion: Assign unique IDs to existing rows.
Tick the box beside several rows in a table and a bar appears. It sets one property across every ticked row at once — the fastest way to re-triage a backlog.
Boards are where most of Notion's day-to-day feel lives, so they carry the most options. All of them are in the ⚙ Settings dialog.
soft colours the count and lets the drop through (the default — a
limit is a signal, not a rule), ask confirms first, strict refuses.This is the piece Notion charges for.
You never have to write any of this by hand. Type /chart, and you get a
dialog of dropdowns with a live preview — the chart redraws as you change
each setting, so you pick by looking rather than by remembering.
Once a chart is in your note, hover it and click ⚙ Edit chart to change it. Same dialog, pre-filled with what that chart currently does.
Behind the scenes a chart is a code block. You can edit it directly if you prefer, but the dialog writes every one of these keys for you.
```notion-chart
database: Tasks
chart: donut
group: status
aggregate: count
title: Where my tasks are
```
| Key | What it does |
|---|---|
database |
Which database. Required. |
group |
The property whose values become bars or slices. Required. |
chart |
column, bar, line, area, pie, donut, scatter. Default column. |
aggregate |
count, sum, average, median, min, max, count_unique, percent_checked. |
value |
Which numeric property to aggregate. Not needed for count. A rollup counts as numeric here. |
series |
Split into multiple coloured series by a second property. |
stacked |
true to stack the series instead of placing them side by side. |
filter |
Same syntax as a view's filter. |
sort |
value_desc (default), value, label or none. |
limit |
Only chart the top N groups. |
height |
Chart height in pixels. Default 340. |
legend / values |
false to hide the legend or the data labels. |
Some charts worth stealing — paste these, or build them in the dialog:
Spending by category this year:
```notion-chart
database: Expenses
chart: bar
group: category
value: amount
aggregate: sum
filter: [Date after 2026-01-01]
sort: value_desc
title: Where the money went
```
Tasks completed per month, split by priority:
```notion-chart
database: Tasks
chart: column
group: due
series: priority
aggregate: count
stacked: true
filter: [Done is true]
```
Habit consistency:
```notion-chart
database: Habit log
chart: line
group: date
value: completed
aggregate: percent_checked
sort: label
```
Charts re-draw whenever the underlying notes change, take their colours from your Obsidian theme, and are drawn as plain SVG — no chart library, nothing loaded from the internet. Hover any bar or slice for exact numbers.
Grouping by a date buckets into months automatically, since a chart with one bar per day is unreadable.
A chart answers "how is this distributed?". A widget answers "what's the one
number?" — the small, glanceable things a Notion dashboard is made of. Type
/widget and pick a kind.
| Kind | Shows |
|---|---|
metric |
A single figure: a count, a sum, an average, a percentage |
progress |
A bar: how much of a filtered set is done |
countdown |
Days until the next date in a property |
list |
The top few rows, as links |
button |
A button that adds a row with values already filled in |
```notion-widget
kind: metric
database: Tasks
aggregate: count
filter: [Status is not Done]
label: Still open
```
Every kind takes database, filter and label; metric adds aggregate,
value, prefix and suffix; progress adds a total filter to divide by;
countdown takes a date property or a literal target; list takes a limit.
Run Notion: Set this note's icon and cover (or /cover). Pick an emoji and a
cover image, and they appear above the note's title the way a Notion page header
does. Both are stored in the note's own frontmatter.
Turn the whole feature off, or change the cover height, in Settings.
```notion-toc
```
Builds a live list of the note's headings. notion-breadcrumb does the same for
the note's folder path, as clickable links.
A heading that folds the section underneath it — /toggle heading 1. Notion's
single most-used organising device, and markdown has no equivalent.
Markdown has no side-by-side syntax, so this adds one. Separate the columns with a line
containing ===:
```notion-columns
### Today
- [ ] Ship the thing
===
### This week
- [ ] Plan the next thing
```
Each column is rendered as normal markdown, so links, embeds and even database views work inside one. Columns collapse to a single stack on narrow screens.
Export from Notion as Markdown & CSV. You'll get a folder per database, with one
.md file per row and the properties already in frontmatter — exactly the shape this
plugin expects.
Import it: Settings → Notion Suite → Import folder. Point it at the exported folder. It reads every note's frontmatter, guesses a property type for each key it finds, and collects the distinct values of select properties into options.
Then drop a view block into any note and you're running.
What carries over well: properties, select options, dates, checkboxes, relations by title, sub-items, page content, and nested pages.
What differs, honestly:
if(), dateBetween() and string functions
aren't here. Rollups, though, are fully supported — see
Relations and rollups./embed) is the nearest thing.Everything else in this README works today.
Worth stating plainly, since the community directory publishes a behaviour report for every listed plugin:
eval — a build-failing test enforces that.You do not have to take that on trust. The directory's review reports no
suspicious network patterns, no obfuscation, and reproduces the released
main.js byte-for-byte from this repository — so the code you can read here is
provably the code you are running.
/ menu on or off./.Databases.Bind any of these to a key in Settings → Hotkeys.
| Command | What it does |
|---|---|
| Show the databases sidebar | Opens the sidebar |
| Open a database | Jumps to a full-page database |
| New database | The create dialog |
| Insert database view | A view block in the current note |
| Insert chart from database | A chart block |
| Add item to a database | A new row, without leaving the note |
| Add a property to a database | The property dialog |
| Turn a folder into a database | Import |
| Set this note's icon and cover | The page header dialog |
| Toggle the checkbox property on the active row | Tick a to-do from inside the note |
| Assign unique IDs to existing rows | Backfills a Unique ID property |
| Toggle the slash menu | On/off without opening settings |
| Refresh all database views | When something looks stale |
"No database called X" — the name in database: must match the database name in
settings, not the folder name. Settings → Notion Suite lists them, and each has a
"Copy view block" button that gets it right for you.
The block shows as raw text — you're in edit mode. Press Cmd/Ctrl+E, or click
outside the block; Obsidian renders code blocks in reading mode and in live preview when
the cursor is elsewhere.
A board says it needs a group property — boards group by a select, status,
multiselect or checkbox property. Add one, then set group: <property>.
A property isn't showing — check the id matches the frontmatter key exactly
(case-sensitive). The key in the note is what's read; the property's name is only a
label.
Changes not appearing — run the command Notion: Refresh all database views. Views normally update themselves when files change.
Edits aren't saving — the note is probably open in another pane with unsaved changes. Close the duplicate pane.
It's not in Browse — Obsidian caches the plugin list. Close and reopen the Browse dialog, or restart Obsidian.
npm install
npm run dev # rebuild on save
npm run build # typecheck, then a production bundle
npm test # the full test suite
npm run lint # eslint, including eslint-plugin-obsidianmd
npm run typecheck # types only
For live development, symlink the project folder into
<vault>/.obsidian/plugins/notion-suite so npm run dev rebuilds straight into
your vault. The Hot Reload plugin will then pick
up each rebuild without restarting Obsidian.
Before you change anything, read CONTRIBUTING.md. It is not style advice: every rule in it exists because the same bug shipped more than once, and each is backed by a check that fails the build.
src/
main.ts plugin entry: code blocks, commands, settings, lifecycle
types.ts the data model
settings.ts settings tab
db/
store.ts schemas; folder-of-notes <-> rows; all frontmatter writes
query.ts filtering, sorting, grouping, date buckets
value.ts property coercion, formatting, comparison
formula.ts the formula language: tokenizer, parser, evaluator
rollup.ts gathering related values and collapsing them
resolve.ts rollup/formula resolution, with cycle protection
calculate.ts table footer calculations
tree.ts sub-items: building and walking the hierarchy
order.ts manual ordering positions
views/
renderer.ts toolbar, search, dispatch to a view
viewState.ts per-view state that has to survive a re-render
table.ts board.ts gallery.ts list.ts calendar.ts timeline.ts
cells.ts the editable cell widgets
config.ts parses ```notion-db and ```notion-chart blocks
blockEdit.ts writing settings back into a note's code block
sidebar.ts the databases sidebar
databaseView.ts full-page databases
pageBlocks.ts table of contents and breadcrumbs
columns.ts the ```notion-columns block
charts/
aggregate.ts rows -> chart data
svg.ts chart data -> inline SVG
widgets/
widget.ts parsing ```notion-widget blocks
render.ts drawing them
slash/
commands.ts the command catalogue and its matching
suggest.ts the / menu itself
ui/
modals.ts create database, insert view, insert chart, import folder
propertyModal.ts create/edit/delete a property, including rollups
confirmModal.ts confirm() and choose() dialogs
bulkModal.ts setting one property across many rows
pageBanner.ts page icons and covers
templates.ts the nine starter databases
tests/
logic.test.ts query, chart, config and value engines
rollup.test.ts rollup aggregation, relation matching, cycle safety
formula.test.ts the formula language
widget.test.ts widget parsing and rendering
roundtrip.test.ts anything rendered to text and read back must survive it
guards.test.ts build-failing checks for mistakes this repo has made
view.test.ts views rendered into a real DOM and driven like a user
interact.test.ts dialogs, panes and drag-and-drop
dom.ts a DOM plus Obsidian's element helpers
obsidian-stub.ts a stand-in for the Obsidian API
tests/dom.ts gives the suite a real document and the helper methods Obsidian
adds to HTMLElement, so the plugin's actual renderers run in CI: a test can
type into the search box, press "next month", drop a card or dismiss a dialog and
then check what the user would be looking at. Every one of those tests was
written by reintroducing the original bug and watching it fail.
The harness is a model of Obsidian, though, and a wrong model passes its own tests. What turns on Obsidian's real semantics — a real drag gesture, when a post-processor re-runs — plus anything visual stays in docs/SMOKE.md, which is the short manual checklist to walk before a release that touches views or panes.
Publishing is driven by the version in manifest.json, not by tags:
npm version patch # or minor / major
git push
npm version bumps manifest.json and versions.json, and the push triggers a
workflow that typechecks, lints, tests, builds, attests the build and publishes a
GitHub release whose tag matches the manifest version — which is what Obsidian's
updater reads. A push that does not change the version releases nothing.
MIT