Skip to content

Typed properties

A type says what a thing has: a task has a status and a due date, a book has an author and a rating. Declare one and Subspace gives you a small form for those fields, warns you when a value does not fit, and makes the values queryable.

The values themselves stay where you can see and edit them: as ordinary key:: value child bullets in the outline. The form is a convenience over those bullets, not a separate store, so you can always type a property by hand instead.

Create a page inside the types directory and give it a metadata.type object. Open the metadata editor with ⌘⇧M and write, for example:

{
"type": {
"tag": "task",
"fields": [
{ "name": "status", "kind": "select", "options": ["todo", "doing", "done"], "required": true },
{ "name": "due", "kind": "date" },
{ "name": "owner", "kind": "ref" }
],
"defaults": { "status": "todo" }
}
}
Key Meaning
tag The #tag that binds this type to a bullet. A leading # is optional.
namespace A directory slug. Every page directly in that directory gets these fields.
fields Each has a name and a kind: text, number, date, boolean, select, ref, or relation. select also needs options; any field can be required.
defaults Per-field starting values, shown as placeholders.
strict false by default. See validation.
  1. Tag a bullet

    Write #task anywhere in a bullet. That bullet is now a typed node, and a row of property chips appears beneath it.

  2. Or put a page in a namespace

    If the type declares a namespace, every page directly inside that directory gets a Properties panel at the top, with no tagging needed.

  3. Fill in the fields

    Use the form, or write the child bullets yourself: status:: doing. Both produce exactly the same thing. Clearing a field in the form deletes the bullet.

A ref or relation field accepts a page title and wraps it as [[Title]] for you. Those references become real links, so a page shows up in the backlinks of anything that points at it through a property.

Typed properties are queryable view fields: filter, sort, and group by status or due the way you would by any other field. Numbers compare as numbers and dates as dates, rather than as text. A { type: 'task' } source selects the tagged bullets themselves, and matches on the type page’s slug, its title, or its tag.

An out-of-range value never blocks your edit. It is accepted, and a Property warning banner appears on the page naming the field and what it expected, for example task.status must be one of todo, done. Fix the value and the warning clears.

Set "strict": true on a type to reject invalid writes outright instead.

Plugins can also declare types, in their manifest, with a JSON Schema. Both kinds show up in the same Properties panel, but they are not the same mechanism, and the differences matter:

Types you write in types/ Types a plugin declares
Binds bullets by #tag Yes No, pages only
Binds pages by directory Yes, via namespace Yes
Where values live key:: value bullets in the outline The page’s JSON metadata
Can reject invalid writes Yes, with strict No, always advisory

If both apply to a page, yours wins.