Fields & input
Once a note is bound to a fileClass (Schema layer), Fileclass gives
you typed, validated input for its fields. This page covers the first wave of
field types and the commands that set values. Everything is written to
frontmatter only, one processFrontMatter write per action.
Available field types
Every type name below links to the section that covers it.
| Type | Stores | Input | Validation |
|---|---|---|---|
| Input | text | text prompt (or a guided template) | must be scalar text |
| MultiInput | list of text | list editor (add/remove/reorder; each item plain or templated) | a list of scalar text items |
| Number | number | text prompt with − / + buttons stepping by step | numeric; optional min/max |
| Boolean | true/false | toggle | boolean |
| Select | one value | value picker | must be an allowed value (if a list is defined) |
| Cycle | one value | one click advances to the next value (the gesture) | must be an allowed value |
| Multi | list | toggle list | each item must be allowed |
| Date | date | date picker | YYYY-MM-DD (unless a custom format is set) |
| DateTime | date+time | date-time picker | YYYY-MM-DDTHH:mm |
| Time | time | time picker | HH:mm |
| Duration | length of time | duration builder | an RFC 5545 DURATION (P1W, PT1H30M) |
| CycleDuration | list of durations | duration list editor | a list of durations |
| Location | "lat,lon" | coordinate inputs + paste | lat −90..90, lon −180..180 |
| Icon | icon id | searchable icon grid | a registered icon id |
| Color | CSS color | swatch palette + custom | a valid CSS color |
| File | link | note picker | a link string |
| MultiFile | list of links | toggle list | a list of links |
| Media | link | file picker (with thumbnails) | a link string |
| MultiMedia | list | toggle list | a list of links |
| Object | nested object | draft editor | each known child validates |
| ObjectList | list of objects | draft editor | each item’s children validate |
| JSON | free-form value | monospace textarea | must parse as JSON |
| YAML | free-form value | monospace textarea | must parse as YAML |
| Canvas | list of links | auto-filled from a .canvas | — |
| CanvasGroup | list of group names | auto-filled from a .canvas | — |
| CanvasGroupLink | list of links | auto-filled from a .canvas | — |
Empty values are always valid — a field is optional unless a constraint says
otherwise. Lookup and Formula (computed fields) are out of scope for
Fileclass — use Bases views for reverse relations and computed columns.
One value or several
Four of those types hold a list rather than a scalar, and they split by who
decides what may go in it. Multi offers the values the class allows, as a switch
each — clicking anywhere on a row flips it, not just the switch — and stores
them in the order the class declares, not the order you picked them.
Every multi-select opens with a filter box, focused, so a list of hundreds is narrowed by typing rather than by scrolling. Enter flips the first match and clears the box, which chains: type, Enter, type, Enter. Filtering only changes what is shown — values ticked while hidden are still saved.
Two controls make a long list survivable in the other direction, when the problem is undoing rather than finding:
- the icon at the end of the filter row shows only the ticked values, turning a list of hundreds into the handful you actually chose;
- Unselect all (bottom left, carrying the count) unticks everything — not just what the filter shows, which is why it names the number it is about to clear.
In “only ticked” mode a row you untick stays where it is instead of vanishing under
the pointer; the list settles the next time you type or leave the mode. MultiInput takes a list nobody can enumerate in advance: you type
each item, reorder them, and blank rows are dropped on save. Either way the
frontmatter gets a plain YAML list, so a base can filter on it.
Boolean is the simplest of them: no options, a switch for input, and a real
true/false in the frontmatter, which Obsidian renders as a checkbox in its own
properties editor. Note that an empty boolean is not false — it says nothing,
which is why insert missing fields leaves it blank rather than guessing.
Required fields
Watch: Fields you can't leave empty Fileclass #023 · 1:54 · recorded with 0.2.6 · on YouTubeAny field can be marked Required in the schema editor (the toggle sits with
the common field options, alongside the name and type). A required field with an
empty value is reported as a violation, and the flag is visible without opening
anything: a schema row reads File · required, and where the field has no value its
own action icon turns red — in a note’s fields and in Obsidian’s Properties panel
alike. The control you would use to fill it is the one that says it is missing; its
tooltip spells out required.
Nothing is ever blocked. A note that violates its class can be saved, left, and come
back to — required is a statement about your model, not a gate on your typing. What
it changes is where the violation surfaces:
- the validation columns of the editable
fileclass-tableview, fileclass validateon the CLI (non-zero exit on any violation), and the API’svalidate(scope), andsetValue/set-where, which refuse to write an empty value into a required field.
Non-empty values keep their normal per-type validation (a number stays numeric,
a Select must still be an allowed value, and so on).
A key is not a value. Insert missing fields writes the key with nothing in it
(author: ""), which leaves the note in violation — that is the point of the flag.
Conversely a field’s Clear removes the key altogether. In a base, the filter that
catches both cases is author.isEmpty(): !author matches nothing, and
author == "" misses the note where the key is simply absent.
The order of a note’s properties
Obsidian’s frontmatter writer appends: a key that was not there lands at the end,
whatever position the class gives it. So Insert missing fields on a note that already
carried properties puts the new ones after the old, and a field added to a class months
later lands last on every note it reaches. The class knows the order — its fields list,
reorderable in the schema editor — and the fields modal and generated views honour it. The
file was the one place that did not.
Reorder frontmatter to match the class puts it back: a command, an entry in a note’s right-click menu, and a ⇅ Reorder properties action beside Add property — the last two appear only when the note is actually out of order, so their presence is the message. Turning on Reorder frontmatter when inserting fields does it right after an insert, which is where the disorder is created in the first place.
Three things to know before you run it on a vault:
- A reorder is a rewrite. Key position is not addressable, so the whole frontmatter block is written again — which drops YAML comments, exactly as any property write already does. Nothing else changes: values are re-attached as they were, block scalars, dates and nested objects included.
- Nothing to do, nothing written. A note already in its class’s order is not touched at all: no write, no modification time, no diff.
- Integer-like keys stay put. YAML sorts keys like
2024numerically whatever we ask, so a field with such a name will not follow the plan. The notice says which ones.
Keys no class declares — tags, aliases, cssclasses, the fileClass key itself,
anything you wrote by hand — are never dropped and never reordered among themselves. Where
they sit is a setting: first (the default), last, or exactly where
they already are.
Number fields
Watch: Number, and why it isn't text Fileclass #003 · 1:52 · recorded with 0.1.1 · on YouTubeNumber stores a real number in the frontmatter — pages: 412, not pages: "412" —
so a base can sort, filter and total it.
Its options are Min, Max and Step. The input is a plain text field with
its own − and + buttons (and the ↑/↓ keys) stepping by step, 1 by default:
- on an empty field the first − or + shows
Minitself (0 when no minimum is set), whichever button you press — one click, one value the field accepts — and the buttons step normally from there; - the value is clamped to
Min/Max, and a fractional step stays clean — stepping 0.1 by 0.2 gives 0.3, not 0.30000000000000004.
Typing is never blocked: enter twelve and the field keeps it, then validation
refuses the save with “pages” must be a number. A native number input would have
dropped those keystrokes silently, leaving an empty field and no explanation.
Input templates
Watch: A field with a fixed shape Fileclass #016 · 2:53 · recorded with 0.2.1 · on YouTubeA template is a shape, not a list of allowed values. For “this field may only be fiction, non-fiction or essay”, the field type is
Select(one value) orMulti(several), and the values come from its Values list — or from a note, a folder, or a base view: see Where allowed values come from. Reach for a template when every value follows the same pattern instead — a URL, a reference number, an amount with a unit.
An Input field can define a Template in its options so its value follows a
fixed structure instead of being typed by hand. The template is a plain string
with placeholders:
{{name}}→ a free-text sub-input for that part.{{name:["a","b"]}}→ a dropdown limited to that JSON array of choices.
For example, a repository field with the template
https://github.com/{{user}}/{{repo}}/, or a price field with
{{amount}} {{unit:["gp","sp","cp"]}}.
When a template is set, editing the field opens a guided form — one control
per placeholder plus a live Result preview you can still fine-tune by hand.
A value already in the note is read back into its parts, so correcting one part
keeps the others; a value that doesn’t fit the template (typed by hand, or stored
before the template existed) leaves the controls empty instead. Either way the value
as it stands is shown as Current value above the controls, and stays there while
you type — the preview is the value you are building, not the one you had.
The stored value stays a single text scalar (the rendered string): a
templated Input is still an Input, with no computation and no Bases
dependency. A placeholder name that appears more than once is driven by a single
control. If a dropdown’s choices aren’t valid JSON, that part falls back to a
free-text input.
MultiInput — a list of templated values
MultiInput is the list-valued counterpart of Input: it stores a YAML list
of text scalars (one per item) and reuses the same Template option. Editing
opens a list editor to add / remove / reorder items; each item is entered
through the same guided sub-form as Input (placeholder controls + preview), or
a plain prompt when no template is set. Blank items are dropped on save. Use it
when several values share one shape — e.g. a list of repository URLs from a
https://github.com/{{user}}/{{repo}}/ template.
Multi-line Input
Some Input fields hold a paragraph rather than a phrase — a summary, an abstract,
a rationale. The Multi-line option (in Options on the field) opens a text area
instead of the one-line prompt: you see what you have written, Enter wraps, and
the vault’s text font is used rather than the monospace of the JSON editor.
Nothing else changes. The value is still one string, validated as any other Input,
written as a YAML block scalar when it holds line breaks:
overview: |-
First line.
Second line, after a real break.
MultiInput takes the same option, and applies it to each item: the list editor
around them is untouched. A template wins over Multi-line when both are set,
since a template is a shape made of single-line parts.
Link fields (File / Media)
Watch: Link a note to another note Fileclass #012 · 1:35 · recorded with 0.1.1 · on YouTubeFile, MultiFile, Media, and MultiMedia store wikilinks. Their candidate
list comes from a Base view — configure the field with a .base file and view
(baseFile + viewName); this replaces Metadata Menu’s Dataview query and Media
folders. An optional displayColumn (a base column id such as note.title) sets
the alias shown in the picker and written into the link.
- Candidates appear in the view’s own order — its
sort:(thengroupByflow) — so the picker matches how the base reads, instead of an arbitrary order. Alimit:on the source view also applies. The same holds forSelect/Multifields sourced from a base. - When the source view defines a
groupBy, the picker shows those groups: headers in the single-pick suggester (they keep delimiting the list as you type) and section headers in the multi-pick list. The keyless group (files with no value for the grouping property) reads (No value). - When no base is configured, or the core Bases plugin is unavailable, the picker gracefully falls back to all notes (File) or all media files (Media).
MultiFile/MultiMediapick several at once: the same switch list as aMultifield (a click anywhere on a row flips it), pre-ticked with what the note already holds. The stored order is the view’s, not the order you switched them on — so a sorted view gives sorted links.
The value is always a plain link, never an embed (
![[…]]). Metadata Menu offered that, and it made sense there: its fields could live inline in the note body, where an embed renders. In frontmatter it renders nothing — and worse, Obsidian doesn’t register an embedded value as a link, so renaming the image leaves it dangling (a plain link is rewritten for you), it is absent from the graph, and a Basesimagecolumn ignores it. Values already stored as embeds keep resolving everywhere Fileclass reads them.An image value shows as a thumbnail wherever Fileclass displays it, and how much room it gets depends on what the surface is for:
- the pickers are galleries — four per row, the picture large, the name under
it — because a cover is chosen by looking at it, not by reading
cover-final-2.png. Picking several works the same way: no switches, a ticked card is outlined, and the filter box above still narrows the grid; - a table cell shows the picture alone: the file name beside it says nothing the picture does not, and in a table the name is what takes the width. It stays available on hover;
- the Properties row and the note-fields modal show the thumbnail beside the link, which there is worth keeping — that is where you check which file a value points at.
A list shows its first three. Images only: the media types also accept audio, video and PDF, and there is no honest thumbnail for those — those keep their names everywhere, since the name is all there is.
- the pickers are galleries — four per row, the picture large, the name under
it — because a cover is chosen by looking at it, not by reading
- Links honor your vault’s link settings (
generateMarkdownLink).
Conditional candidates (dependent fields)
Watch: Candidates that depend on another field Fileclass #015 · 1:57 · recorded with 0.1.1 · on YouTubeA link field’s candidate list can depend on the value of another field of the note you are editing — and Fileclass writes the plumbing for you.
In the field’s options, under the base and view, set:
- Depends on another field — the field of this fileClass whose value narrows the list. Only single-valued fields are offered: comparing a list to a scalar needs a different predicate (see by hand below).
- Match on property — the property on the candidate side to compare against it. It defaults to the source field’s name, which is the usual case.
A preview shows the view that will be created and the formula behind it, so you see
the predicate before saving rather than discovering it later inside the .base. On
save, Fileclass adds them to the bound base and points the field at the generated
view — a narrowed copy of the view you chose, keeping its filters, sort and
order:
formulas:
fcMatch_publisher_by_publisher: publisher.isTruthy() && this.publisher.isTruthy() && (publisher == this.publisher)
views:
- type: table
name: All series # yours, untouched
filters:
and:
- fileClass == "Series"
sort:
- property: started
direction: ASC
- type: table
name: "Fileclass · All series · publisher = this.publisher"
filters:
and:
- fileClass == "Series" # the scope, inherited
- formula.fcMatch_publisher_by_publisher == true # the predicate, added
sort:
- property: started
direction: ASC
Four things worth knowing about what it generates:
- it narrows your view rather than replacing it. That scope matters: a generated view filtered on the formula alone would match anything in the vault sharing the value — a comic published by Casterman would show up among Casterman’s series;
- the comparison shape follows the source field’s type: a link field is compared by basename, anything else by value;
- the view’s name carries the scope it narrows, so two fields narrowing different views the same way don’t collide, while the formula is named after the predicate alone and is shared;
- your base is otherwise untouched — other views, other formulas, and the tuning inside the generated view (column widths, say) all survive a regeneration.
A field whose candidates are notes of its own fileClass will offer the edited note
itself; add file != this.file to the generated view if that bothers you.
Writing the predicate by hand
Still available, and still the way to express what the builder doesn’t — a
multi-valued source (this.<field>.contains(…)), or a comparison the two types
don’t agree on. When the picker opens, Fileclass runs the bound Base view
with the current note as its context, so this inside the view’s filters and
formulas resolves to that note — not only this.file, but its frontmatter
properties too (this.<PropertyName>). Write a view filter that compares each
candidate row against this.<PropertyName> and the list narrows to the rows that
match the note’s current value.
For example, an Objective note has a Goal link field and a Project field,
and you want Project to offer only the projects attached to the goal already
chosen in Goal. In the projects .base, add a formula and a dedicated view:
formulas:
# true when this project's Goal is the same as the edited note's Goal
isThisGoal: Goal.isTruthy() && this.Goal.isTruthy() && (file(Goal).basename == file(this.Goal).basename)
views:
- type: table
name: Goal's projects
filters:
and:
- formula.isThisGoal == true
Then point the Project field at that view (baseFile + viewName). Note the
two distinct references: the unqualified Goal is each candidate row’s own
property, while this.Goal is the Goal property of the note being edited — a
common pitfall is writing this.file.basename (the edited note’s name) when
you meant this.Goal (its field value).
The .isTruthy() guards are not decorative: file(x).basename on an empty value
yields null, so a bare file(Goal).basename == file(this.Goal).basename
reduces to null == null — true — whenever both sides are empty. Without a
guard, opening the picker before the source field is set would surface every
candidate that also has an empty value. A single guard on either side removes
this false match; the example keeps both so it reads as “only when the edited
note has a Goal, match rows that share it”.
Two requirements: the field the filter
depends on must be saved first (the picker reads it from the frontmatter, so
set Goal before opening Project), and after changing a field’s bound view you
must save the change in the fileClass settings for the picker to use it.
Warning — the dependency is not reactive. The source field only constrains the candidate list shown when you open the picker. A value already stored in the dependent field is never revalidated, reset, or cleared when you later change the source field. In the example above, if you edit
Goalafter having setProject, the existingProjectvalue stays as-is even if that project no longer belongs to the new goal — leaving the note in an inconsistent state until you re-open and re-pickProjectyourself.
Date fields (Date / DateTime / Time)
Watch: DateTime and Time Fileclass #008 · 2:17 · recorded with 0.1.1 · on YouTubeThree types for three questions: Date is a day, DateTime a point in time (a
day and a clock in one value), Time a time of day with no day attached — the
hour a door opens, every session. Each is stored in its own native form —
YYYY-MM-DD, YYYY-MM-DD[T]HH:mm, HH:mm — and has its own default write format in
the settings.
Editing a date opens a native picker (calendar / clock) with Today and Clear buttons, plus a link toggle:
- Raw text (default) — stores the formatted date, e.g.
2026-07-16. - As link — stores a wikilink, e.g.
[[2026-07-16]]or, with a Link path set,[[Journal/2026-07-16]]. Configure the default state (Insert as link), the Link path and the Link alias in the schema editor.
Linking to a daily note
Watch: Dates that link to your daily notes Fileclass #016b · 1:55 · recorded with 0.2.2 · on YouTubeThe Link path may contain braced moment tokens, which follow the date — so
a link can be filed the way daily notes usually are. Only what’s inside the braces
is formatted, which is why the literal words survive (a raw moment format would
read the D of Daily as a day number):
| Field format | Link path | Link alias | Stored value |
|---|---|---|---|
| (ISO) | Journal/ | off | [[Journal/2026-07-30]] |
YYYY-MM-DD ddd | Daily/Notes/{{YYYY}}/{{MM}}/ | off | [[Daily/Notes/2026/07/2026-07-30 Thu]] |
YYYY-MM-DD ddd | Daily/Notes/{{YYYY}}/{{MM}}/ | on | [[Daily/Notes/2026/07/2026-07-30 Thu|2026-07-30 Thu]] |
Link alias writes [[path/date|date]], so the link reads as the date instead
of its whole path. It is skipped when there is no path — [[2026-07-30|2026-07-30]]
would say nothing twice.
Which format is written
Watch: Date: the format you store Fileclass #007 · 2:54 · recorded with 0.1.1 · on YouTubeThree levels, first one wins:
- the field’s own Date format (moment.js tokens), set in the schema editor;
- the plugin-wide default for that type — Default date format, Default datetime format, Default time format. The field editor names the one that applies: “Blank uses default: DD/MM/YYYY”;
- blank everywhere → the native ISO form (
YYYY-MM-DD,YYYY-MM-DDTHH:mm,HH:mm).
Every input where a format is typed — the three settings and a field’s own Date
format — shows what today’s date looks like through it (now → 30/07/2026)
and flags what moment can’t read: YYYY-KK-007 warns that "KK" is not a token
and would be written as-is (wrap literal text in [brackets]). The Link path
gets the same treatment, previewing the whole wikilink it would write today.
All three decide what is written to the file. Nothing reformats a date for
display: a stored date is shown exactly as it sits in the frontmatter (an
insert-as-link value included). The one exception is an object display template,
which may ask for a format per token — {{released|YYYY}}.
That is deliberate: how a date is stored in your vault is your call. Store
30/07/2026, or a wikilink to a daily note, and recover ordering in a base with a
formula (date(...)) when you need to sort or filter on it.
Obsidian’s own property type can overwrite your format
Obsidian keeps its own type for each property (Settings → Properties, or the
menu on a property row). If you set a property to Obsidian’s Date type and then
edit it with Obsidian’s date picker rather than Fileclass’s, Obsidian writes
its own YYYY-MM-DD form — whatever the field’s format says, and dropping a
wikilink value.
Fileclass can’t prevent that: property-type widgets are global and per-type in Obsidian, so the plugin has no way to intercept the native picker for one field without hijacking every date property in the vault. Keep such properties on Obsidian’s Text type and edit them through Fileclass’s button, which is the one that knows your format.
If the Natural Language Dates plugin is installed, an extra field parses phrases like “next friday” into the picker.
Durations & interval cycling
A duration is stored as an RFC 5545 string (PT45M44S, P90D) — the interoperable form,
and not one anybody reads. Wherever Fileclass shows a value it shows the reading instead
(45m 44s); in Obsidian’s own Properties panel, where the stored string is what the
editor holds, the reading is added next to it rather than replacing it, so nothing can
write the reading back into your note. An interval sequence is a list of pills there, and
each pill carries its own reading — P180D 180d — left of its remove button.
A Duration field stores a length of time as an RFC 5545 DURATION string —
unlike a Time field it doesn’t wrap at 24h, so it fits prep times, effort
estimates, brew times, etc.:
prep_time: PT1H30M # 1 h 30 min
total_time: P1DT6H # 1 day 6 h
brew: P2W # 2 weeks
Editing opens a builder: either type the duration directly — ISO
(PT1H30M) or a human form (1h 30m, 2w, 1 day 6 hours) — or use the
weeks / days / hours / minutes / seconds spinners. The two stay in sync, with a
live compact preview (1d 6h); the stored value is always the canonical ISO
form. Output stays RFC-5545-valid (weeks stand alone; otherwise they fold into
days). No runtime dependency.
A field can also define preset durations in its schema (a list you build
once). They then appear as quick picks when entering a value: a one-click
button on a Duration field, and chips you tap to append on a CycleDuration
list (you can still reorder, add a custom one, or repeat a preset). Presets are
a convenience — values are still stored per note.
An interval sequence (CycleDuration)
Watch: CycleDuration: a sequence, not a duration
Fileclass #010 · 1:42 · recorded with 0.1.1 · on YouTubeCycleDuration stores an ordered list of durations — an interval sequence. The
order is the schedule: it is what a linked date field walks through, one interval per
click. Its presets are the class’s vocabulary of spans (say 90, 180 and 360 days
for a re-read cycle); the sequence itself is each note’s own composition — two of
them, all three, or the same one twice.
Set next date (spaced repetition)
Watch: Set next date Fileclass #010b · 1:46 · recorded with 0.1.1 · on YouTubeThis is how you schedule a date that moves forward by your own sequence of intervals (spaced repetition, recurring reviews, chores):
Add a
CycleDurationfield (e.g.next interval) and enter your intervals in order — say1 day,1 week,2 weeks,5 weeks.On a
Date(orDateTime) field (e.g.next session), open its Next interval field option and pick that field. The dropdown lists theDurationandCycleDurationfields of the fileClass — its own and its inherited ones — so there is no name to remember and no incompatible type to choose by mistake. Leave it on(none)for a plain date.Editing the date now shows a Set next date button. One click:
- computes
current date + first interval— from the date already stored, falling back to today when the field is empty, - writes it to the date field, and
- rotates the interval list so the next click uses the following interval, wrapping back to the first after the last.
The picker closes on success, so each step of the schedule is one deliberate gesture.
- computes
Or skip the picker: Alt-click the date’s control — in the Properties editor, the note-fields modal or a table cell — and it advances straight away. Holding Alt over the control shows the date it would write.
So repeatedly clicking next session walks the date through +1d, +1w, +2w,
+5w, then +1d again. Pointing the option at a plain Duration field
instead gives a fixed interval (added every time, no rotation).
Whichever route you take, the date is stored exactly as the picker would store
it: the field’s own dateFormat, and its link shape when the field defaults to
links or already holds one — so a [[Daily/Notes/2026/10/2026-10-29 Thu|…]] stays
a link instead of collapsing to a bare date.
If the option was set and the interval field has since been renamed, retyped or
removed, the dropdown keeps that name and marks it (not found): the stored
value isn’t dropped behind your back, and the missing Set next date button
now has a visible reason.
It is a manual, one-shot action — no automatic recomputation and nothing touches other notes, so it stays within Fileclass’s guided-input scope (computed fields remain out of scope).
Location
Watch: A place on a map Fileclass #019 · 3:04 · recorded with 0.2.2 · on YouTubeA Location field stores geographic coordinates as a "lat,lon" scalar — the
convention map plugins read:
location: "48.8566,2.3522"
Editing gives two range-validated number fields (latitude −90..90, longitude −180..180) plus a paste box that fills them from whatever you copied:
| pasted | read as |
|---|---|
48.8584, 2.2945 · 48.8584 2.2945 · 48.8584; 2.2945 | the pair |
48.8584° N, 2.2945° E | the pair, with S/W negative |
a Google Maps link (/@lat,lon,17z, ?q=lat,lon) | the pair |
an Apple Maps link (?ll=lat,lon) | the pair |
an OpenStreetMap link (?mlat=…&mlon=…, #map=15/lat/lon) | the pair |
geo:48.8584,2.2945 | the pair |
Anything else says so instead of filling nothing, and a pair that is off the globe says that rather than pretending it couldn’t be read. Open in map opens the coordinates on OpenStreetMap in your browser.
No embedded map picker. An in-app map means loading remote tiles, which is against Fileclass’s no-remote-resources stance (and an Electron
<webview>proved unstable). To pick a new spot, use Open in map or any map site in your browser, then copy thelat,lonfrom the URL and paste it here.
Seeing them on a map
Bases has no map layout of its own. Maps, a community plugin by the Obsidian team, adds one — install it from Settings → Community plugins → Browse, search Maps.
Then open a base, add a view, set its type to Map, and set Marker
coordinates to your Location field. Markers appear for every note the view’s
filters return, and follow those filters as they change. The view options also let
each marker take its icon and colour from properties — which is what an
Icon and a Color field on the same class are for.
Fileclass makes the property easy to enter correctly and validates it; drawing the map is the map plugin’s job.
Icon
Watch: Pick an icon Fileclass #017 · 2:08 · recorded with 0.2.2 · on YouTubeAn Icon field stores an icon id (a single scalar), chosen from a visual
picker:
icon: map-pin
Editing opens a searchable grid of real icon previews; click one to pick it,
or clear the value. The icons come from an extensible source (field option
Icon source): Lucide (the default, bundled with Obsidian) or all
registered icons (Lucide plus any icon other plugins register). Rendering uses
Obsidian’s getIconIds() / setIcon() — no bundled icon list, no dependency.
The stored value is the bare id (map-pin, not lucide-map-pin). Naming an
Icon field icon (with Lucide icons) lets the core Bases Map view use it
as the marker icon — Fileclass just makes the property easy to enter.
Color
Watch: Colors, and your own palette Fileclass #018 · 1:54 · recorded with 0.2.2 · on YouTubeA Color field stores a CSS color value (a single scalar):
color: "#fb464c"
Editing opens a picker with palette swatches (field option Color source —
the Obsidian Canvas palette by default; extensible to more palettes) plus a
custom color: a native color input and a text field accepting any CSS value
(hex, rgb(), or a color name). Click a swatch to pick it, or clear the value.
Uses a native input + CSS — no dependency.
Like Icon, the palette is a picker convenience; the stored value is a raw CSS
color. Naming a Color field color lets the core Bases Map view use it as
the marker color.
Where allowed values come from
Watch: Select: the values you allow Fileclass #004 · 2:07 · recorded with 0.1.1 · on YouTubeSelect, Cycle, and Multi draw their allowed values from the field’s option
source:
- Inline list — values listed directly in the field definition.
- From a note — the non-empty lines of a note (
valuesListNotePath). - From a Base view — the values come from a
.baseview (replaces Metadata Menu’s Dataview source). By default they are the matching files’ names; set a Column (e.g.note.title, a formula column) to use that column’s distinct values instead. Requires the core Bases plugin; if it is unavailable the field falls back to free entry.
When no list is defined, the field accepts free text (and Multi accepts a
comma-separated entry).
Commands
- Fileclass: update a field in current file — pick one of the note’s fields and set its value with the type-appropriate input. The picker shows each field’s current value.
- Fileclass: insert missing fields in current file — adds every root field of the note’s fileClass(es) that isn’t already in the frontmatter, each with an empty default, in a single write.
Nested fields (Object / ObjectList)
Watch: A group of fields inside a field Fileclass #020 · 3:31 · recorded with 0.2.3 · on YouTubeAn Object field groups typed sub-fields into a nested structure; an
ObjectList is an array of such objects. Sub-fields are declared in the same
fileClass with a path pointing at their parent — nesting can go several levels
deep.
Editing opens a draft editor:
- You edit a working copy in memory. Cancel writes nothing.
- Save validates the whole draft, then writes the entire subtree in a
single
processFrontMattercall. - The editor mutates a clone of your existing value, so unknown keys are preserved — Fileclass never regenerates an object from the schema.
- ObjectList items can be added, edited, reordered, and removed. A new item exists only once its editor is saved — starting one and cancelling leaves the list exactly as it was.
Only root fields appear in the field picker; nested fields are reached by editing their parent object.
A child may carry the same name as a root field — a Book with a publisher, whose
editions each have their own — because a field is identified by its name at its
level. The same holds for inheritance (a subclass overrides a child at that child’s
level) and for excludes, which name a class’s own fields, the root ones; a group’s
children go with their parent.
Sorting and filtering on a child, in Bases
A nested value is real structure, so a base can order rows by it — through a
formula, not a dotted property. Bases resolves neither storage.room nor
note.storage.room as a column or as a sort: property (both leave the order
untouched), while a one-line formula does:
formulas:
Room: note.storage.room
Level: note.storage.shelf.level
views:
- type: table
name: By room
sort:
- property: formula.Room
direction: ASC
- property: formula.Level
direction: ASC
This is the half a well-shaped string can’t give you: Study · A-3 in a plain
Input reads the same on screen and sorts as one opaque piece of text.
An ObjectList answers the same way, and further — measured in a base over three
books, one with three editions:
formulas:
Count: note.editions.length # 3 · 2 · empty where the key is absent
FirstFormat: note.editions[0].format # indexing works, and reaches a child
FirstYear: note.editions[0].year
# Formats: note.editions.map(e => e.format).join(", ") ← renders nothing
So a list is countable, sortable by its count, and reachable item by item. Mapping over it is not available: an arrow function produces an empty cell rather than an error, which is worth knowing before you build a view on one. The property itself, used as a plain column, shows the raw JSON — Bases has no editor for a list of mappings either.
Obsidian has no editor for a nested property, so its Properties panel prints the value and colours it as a warning. When the field is declared as a group and its value validates, Fileclass drops that colour — the value is understood here, even if Obsidian can’t edit it. A group whose value doesn’t fit keeps the warning.
A group’s children are edited from the field itself: open its settings — the schema editor’s Edit, or Alt-clicking its type icon anywhere a value is shown — and use Children. The schema screen keeps its own shortcut for the same thing.
Display template
Watch: A list of grouped fields Fileclass #021 · 3:45 · recorded with 0.2.4 · on YouTubeObject/ObjectList fields have a Display template (in the schema editor) controlling how an item is summarized everywhere Fileclass shows the value — the note-fields modal, the list editor, the property buttons and a table cell:
{{fieldName}}— inserts a child field’s display, e.g.{{designation}} - {{ville}} - {{pays}}.{{dateField|FORMAT}}— a Date child with a moment.js format override (e.g.{{start|DD/MM/YYYY}}); without an override, dates use the plugin’s Default date display format (Settings → Fileclass), or the stored value if that is blank.- A child that is itself an Object uses its own template (recursion).
- No template → the first non-empty child value.
With a template set, the summary also replaces the raw JSON that Obsidian prints for
a nested property in its own Properties panel — Obsidian types a mapping as
unknown and shows it read-only, so nothing is taken away, and the JSON stays in the
tooltip. Without a template, the panel is left exactly as Obsidian renders it.
- For ObjectList, each item’s display is prefixed by its rank (
1.,2.…), and items are separated by a|— punctuation a template is unlikely to carry, so the boundary between two items never reads as part of one:1. Paperback · 1990 | 2. Hardcover · 1965.
Structured fields (JSON / YAML)
Watch: When raw is the honest answer Fileclass #022 · 3:05 · recorded with 0.2.5 · on YouTubeJSON and YAML hold a free-form nested value with no declared schema — the escape hatch for structures Object/ObjectList don’t model. Editing opens a monospace textarea (Cmd/Ctrl+Enter saves); the parser answers as you type, naming the line and column it stumbled on, and clearing the text removes the field.
The two types differ in what they store, which is how you choose between them:
| Stored as | Bases can reach inside | Keeps your formatting | |
|---|---|---|---|
| YAML | the parsed structure (real frontmatter keys) | yes, through a formula (note.credits.producer) | no — Obsidian rewrites it as YAML |
| JSON | the text, as a block scalar (`tech: | -`) | no: it is a string |
So YAML is for structure you will query, JSON for a payload you want kept as it came — an API response, a snippet from elsewhere. Verified against Obsidian: a multi-line string is written as a block scalar, an existing block survives writes to other keys, and what comes back is the exact text.
Changing a field’s type doesn’t rewrite what it holds, so a JSON field can open on YAML
(or the reverse). The editor offers Convert from YAML / Convert from JSON whenever
the text reads as the other notation, and hides the offer as soon as it doesn’t.
Use Object/ObjectList when the shape is known and you want typed, guided input; use JSON/YAML for arbitrary or externally-defined blobs. For the fuller decision — including when the data deserves a fileClass of its own instead — see the Modeling guide.
Canvas fields (Canvas / CanvasGroup / CanvasGroupLink)
Watch: Groups on a canvas as data Fileclass #038 · 3:36 · recorded with 0.2.10 · on YouTube Watch: A field that follows your canvas Fileclass #037 · 3:23 · recorded with 0.2.10 · on YouTubeThese are auto-maintained from an Obsidian .canvas file — you don’t
edit them; the Canvas engine derives their value from the canvas graph and
writes it to frontmatter whenever the canvas changes. Configure each field with
a Canvas file path (and, for Canvas/CanvasGroupLink, a Direction):
- Canvas — links to the notes connected to this note by edges in the canvas (following the chosen Direction: incoming / outgoing / both sides).
- CanvasGroup — the name(s) of the canvas group(s) this note sits inside.
- CanvasGroupLink — links to the notes connected to the group(s) this note is in.
Each field also has conjunctive (AND) filters in the schema editor:
- Edge matching colors / from side / to side / labels — only follow edges matching all set criteria (nothing set = any).
- Node matching colors — only keep target notes whose node has these colors.
- Group matching colors / labels (
CanvasGroup/CanvasGroupLink). - Matching files — restrict targets to the notes returned by a
.baseview (this replaces Metadata Menu’s DataviewJS query — D1).
Unlike Lookup/Formula, this needs no Dataview and has no Bases
equivalent (Bases doesn’t index canvas adjacency).
The engine watches .canvas edits, writes only when a value changed (no churn),
and clears fields on notes that dropped out of the canvas. It is the one surface
that writes frontmatter automatically — toggle it under Settings → Fileclass →
Canvas fields engine.
Writing model
- All reads go through the metadata cache; all writes through
processFrontMatter. No note text is parsed or edited. - Each action is one write. Existing keys, key order, and unrelated frontmatter are preserved (see the migration notes on normalization).
- Clearing a field removes its key.