A typed-note workflow for Obsidian. Define object types in the plugin settings; get auto-reshelve on type: change, native lookups (frontmatter or inline blocks), creation commands per type, and one source of truth for schema, folder, filename, prompts, and visual defaults.
A typical typed-note workflow in Obsidian today threads through four plugins:
| Concern | Plugin |
|---|---|
What fields does a person have? |
Metadata Menu (fileClass) |
Where do person files live? |
Workflow Objects (pathMappings) |
How does a new person get created? |
Templater + QuickAdd |
What happens when you change type:? |
Two manual commands |
Schema collapses all of that into one source of truth (the plugin's Settings → Schema tab) and ships the lifecycle as native subsystems.
- Obsidian 1.4.0 or newer.
- Dataview is optional. It's only needed for Custom lookups that go beyond the built-in query subset (see Lookup query runtime); everything else works without it.
From the community directory (once Schema is listed): Settings → Community plugins → Browse, search for "Schema", Install, then Enable.
Manually: download main.js, manifest.json, and styles.css from the latest release, put them in <your-vault>/.obsidian/plugins/schema/, then enable Schema under Settings → Community plugins.
Settings → Schema → "+ Add type". Each type expands inline:
- Basics: name, extends, folder, filename template, tags
- Defaults: per-type values for whichever frontmatter keys you've designated as auto-refreshed (icon and color by default; configurable globally)
- Fields: inline-expand rows, type-aware widgets (Input, Number, Boolean, Select, MultiFile/File with target type, Date/Time, etc.), with
promptOnCreatelabels for the create flow - Backlinks: per-property auto-generated reverse links (set a Backlinks name on any File/MultiFile property and the target object type gets a frontmatter list of every note that links to it)
- Custom lookups: hand-written Dataview queries when a Backlink can't express what you need
That's it. No YAML files to edit, no JSON to hand-write.
When you change a note's type: value (typing it directly or applying a #type/<name> tag), the plugin atomically:
- Moves the file to the new type's folder
- Strips frontmatter keys not in the new schema
- Adds empty placeholders for missing required fields
- Resets auto-refreshed fields (icon, color) from the new type's defaults
When you run Schema: New <type> from the command palette:
- The plugin prompts for any field with
promptOnCreate - Renders the filename template
- Places the file in the configured folder
- Opens it
Schema gives you two ways to produce a computed list on a note:
A backlink is auto-created when you set a Backlinks name on a global
property of type File or MultiFile. Example: person.organization is a
MultiFile targeting organization. Set its Backlinks name to members,
and every organization note gets a members: list of every person that
links to it.
You do not write a query. The list updates whenever a person's
organization: value changes. Each object type's editor shows a card per
Backlink with a sample of the matching notes and a button to jump back to
the property that defines it.
By default the result is written into the target note's YAML frontmatter:
members:
- "[[People/Alice]]"
- "[[People/Bob Smith]]"Each Backlinks card has an Show in frontmatter (default) / Show as inline block toggle that flips the destination per-backlink without losing the auto-update behavior.
For lists that can't be expressed as a backlink (date-windowed queries, multi-criteria filters, etc.), you can hand-write a Dataview query in the Custom lookups section of any object type. Each row chooses where the result lands:
- Property: written into the note's YAML, same shape as Backlinks.
- In the note body: rendered live via a
schema-lookup <name>code block you place in the note body:
## Recent moments
```schema-lookup
recent_moments
```The block re-renders as data changes; no frontmatter writes, no git noise.
A global setting (autoRefreshedFields, default ["icon", "color"]) lists frontmatter keys that always reset to the type's defaults whenever a note is reshelved or retyped. Add summary if you want a per-type default summary; add any custom key you want pinned to the type. The Defaults section of each type editor shows an input per key in this list.
| Command | Description |
|---|---|
Schema: New <type> |
One per type with a folder set. Prompts → filename → folder → open. |
Schema: Edit field |
Pick a field on the active note (fuzzy), open type-aware editor. |
Schema: Reshelve and clean active file |
Manual reshelve+clean for one file. |
Schema: Refresh frontmatter lookups (vault-wide) |
Re-run all frontmatter-mode lookups across the vault. |
Schema: Show loaded types |
Console summary. |
| Setting | Default | Description |
|---|---|---|
| Auto-reshelve on type change | on | When set, editing a note's type: value triggers reshelve+clean automatically. |
| Auto-refreshed frontmatter fields | ["icon", "color"] |
Comma-separated list of keys that get reset to schema defaults on every type change. |
The Settings tab also shows every loaded type with editable folder / filename / icon / color / fields / lookups, plus any validation issues.
If Dataview is installed, queries execute via its JS API.
If not, a built-in fallback handles a restricted-but-real-world-useful subset:
dv.pages('"FOLDER"').filter(callback)- entry shape- Inside callback: frontmatter access,
current.file.path/current.file.name,.some(),===,&&,||,.includes, string slicing dv.luxon.DateTime.fromFormat(...).toFormat(...)shim for the week-code arithmetic the temporal lookups need
Queries that go outside this subset throw a clear error; no silent failures.
Two types - person and interaction - give you a contact list with a running
log of every touchpoint, and each person's note automatically lists their
interactions in reverse-chronological order.
person:
folder: People
filename: {{firstname}} {{lastname}}
fields:
firstname (Input, promptOnCreate)
lastname (Input, promptOnCreate)
company (Input)
email (Input)
interaction:
folder: Interactions/{{date:YYYY}}
filename: {{date:YYYYMMDD-HHmm}} {{summary|slug}}
fields:
summary (Input, promptOnCreate)
when (DateTime)
with (MultiFile, target: person, Backlinks name: interactions)
channel (Select: email, call, in-person, slack)
The Backlinks name interactions on interaction.with gives every
person note an interactions: list of the interactions that mention them.
No manual maintenance.
Daily notes capture quick reflections; moments are richer per-event records that link to people. Backlinks mean each person's note shows every moment they appear in.
moment:
folder: Moments/{{date:YYYY}}
filename: {{date:YYYYMMDD-HHmm}}
fields:
summary (Input, promptOnCreate: "What happened?")
people (MultiFile, target: person, Backlinks name: moments_with_me)
place (File, target: place)
daily:
folder: Daily/{{date:YYYY}}/{{date:MM}}
filename: {{date:YYYY-MM-DD}}
fields:
weather (Input)
mood (Cycle: bad, ok, good, great)
Run Schema: New moment and the plugin prompts for the summary, then opens the
note in Moments/2026/20260602-1543.md. Add [[Jane Doe]] to the people
field - Jane's note's moments_with_me list updates automatically on the next
metadata refresh.
Two types - permanent for evergreen notes and literature for source-tied
notes - with a references field that wires both back to source material.
permanent:
folder: Zettel
filename: {{date:YYYYMMDD-HHmm}} {{title|slug}}
fields:
title (Input, promptOnCreate)
references (MultiFile, target: literature, Backlinks name: cited_by)
literature:
folder: Literature
filename: {{author}} - {{title}}
fields:
title (Input, promptOnCreate)
author (Input, promptOnCreate)
year (Number)
url (Input)
Each literature note's cited_by list updates automatically when a permanent
note references it.
git clone https://github.com/rephlex00/Schema
cd Schema
npm install
npm run build # one-shot build
npm run dev # esbuild watch
npm test # vitest
npm run deploy # build + copy main.js, manifest.json, styles.css to dev vaultnpm run deploy copies the build into a local vault for testing. Set
OBSIDIAN_PLUGIN_DIR to your vault's plugin folder first, e.g.:
export OBSIDIAN_PLUGIN_DIR="/path/to/YourVault/.obsidian/plugins/schema"
npm run deployEvery push and PR to main runs the build and the test suite via CI. To cut a
release, bump the version and push a matching tag:
npm run version:bump # syncs manifest.json, package.json, versions.json
git commit -am "Release <version>"
git push
git tag <version> # tag must equal manifest.json version, no "v" prefix
git push origin <version>The tag push triggers the release workflow, which builds and creates a draft
GitHub release with main.js, manifest.json, and styles.css attached.
Review the draft and publish it.
MIT.