Schema

unlisted

by rephlex00

One source of truth for typed notes: define object types once, get auto-filing, per-type creation commands, and automatic backlinks.

Updated 1mo agoMIT
View on GitHub

Schema

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.

Why

A typical typed-note workflow in Obsidian today threads through four plugins:

ConcernPlugin
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.

Requirements

  • 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.

Installation

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.

Defining a type

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 promptOnCreate labels 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.

Lifecycle

When you change a note's type: value (typing it directly or applying a #type/<name> tag), the plugin atomically:

  1. Moves the file to the new type's folder
  2. Strips frontmatter keys not in the new schema
  3. Adds empty placeholders for missing required fields
  4. Resets auto-refreshed fields (icon, color) from the new type's defaults

When you run Schema: New <type> from the command palette:

  1. The plugin prompts for any field with promptOnCreate
  2. Renders the filename template
  3. Places the file in the configured folder
  4. Opens it

Computed lists: Backlinks and Custom lookups

Schema gives you two ways to produce a computed list on a note:

Backlinks (the common case)

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.

Custom lookups (advanced)

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.

Auto-refreshed fields

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.

Commands

CommandDescription
Schema: New <type>One per type with a folder set. Prompts → filename → folder → open.
Schema: Edit fieldPick a field on the active note (fuzzy), open type-aware editor.
Schema: Reshelve and clean active fileManual reshelve+clean for one file.
Schema: Refresh frontmatter lookups (vault-wide)Re-run all frontmatter-mode lookups across the vault.
Schema: Show loaded typesConsole summary.

Settings

SettingDefaultDescription
Auto-reshelve on type changeonWhen 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.

Lookup query runtime

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.

Cookbook

Lightweight CRM

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 journal with people + moments

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.

Zettelkasten

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.

Dev / build

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 vault

npm 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 deploy

Releasing

Every 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.

License

MIT.

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.