Extending BOB Workspace Without Code Changes¶
BOB Workspace is still structurally based on the Cadence plugin, but the goal is not to make every vault adopt Cadence's original field names. The plugin should act as a workspace shell over markdown entities, while each vault defines its own data model through canonical schemas and Bases.
This guide explains the intended extension model.
Design Principle¶
The plugin has several layers. Later layers should refine or override earlier layers:
- Built-in fallback model
- Lives in
main.js. - Keeps the plugin usable in an empty vault.
- Should stay small and generic.
-
May still contain some legacy Cadence field names for compatibility.
-
Schema layer
- YAML files in
{vault.schemas.source}. - Canonical source for BOB Workspace entity definitions when schema support is enabled.
- Defines entity identity, location, field keys, types, enums, required flags, create defaults, and lifecycles.
-
Edited visually from Settings -> BOB Workspace -> Schemas -> Data model designer.
-
Base layer
.basefiles in{vault.bases}.- Defines view behavior: visible columns, filters, sort, groupBy, and special Base views.
-
The workspace should reuse these views where practical instead of duplicating view logic.
-
Workspace definition layer
workspace.jsonin the plugin folder next todata.json.- Owns schema enablement, Base/view associations, navigation groups, surfaces, secondary tabs, and workbook export groups.
-
Managed from Settings -> BOB Workspace -> Workspace definition, or reloaded with the
Reload workspace.jsoncommand. -
Import alias layer
- Maps spreadsheet/frontmatter aliases to canonical field keys.
- Useful when importing from Cadence, another CRM, or another vault.
- Defined per schema as
field_aliasesand edited in the Data model designer. - Legacy built-in synonyms remain as compatibility fallback only.
The most important rule: the vault data model should live in the vault, not in plugin code.
Workspace Definition¶
workspace.json is the no-code composition layer. When navigation.groups
is present, it replaces the built-in left-navigation definition. Its
secondaryTabs object replaces built-in inner tab definitions, and
workbookGroups replaces export group choices.
Entity-backed configured surfaces use the generic record table/detail UI.
Existing built-in surface IDs such as home and crm.pipeline retain their
specialized renderer when they are included in configured navigation. A new
specialized dashboard still requires plugin code.
Minimal example:
{
"schemas": {
"enabled": true,
"folder": "{vault.schemas.source}"
},
"bases": {
"order": {
"file": "{vault.bases}/Orders.base",
"view": "Active Orders"
}
},
"navigation": {
"groups": [
{
"id": "operations",
"label": "Operations",
"icon": "blocks",
"module": "operations",
"items": [
{ "id": "operations.orders", "label": "Orders", "icon": "package", "module": "operations", "entityKey": "order" }
]
}
],
"secondaryTabs": {}
},
"workbookGroups": [
{ "id": "operations", "label": "Operations", "entityKeys": ["order"] }
]
}
Use bases.<entity>.file and optional view to make each Base selection part
of the portable workspace definition. The surface Base selectors write this
mapping directly, and Import Bases from settings migrates older choices.
Navigation icons are presentation configuration and therefore live in
workspace.json, except an entity's default icon can be set in canonical
schema YAML. Set navigation.groups[].icon and each item's icon for the
actual rendered group and menu-item icons. The Navigation designer provides a
searchable preview picker sourced from the icon IDs registered by the running
Obsidian application.
Secondary tabs are a deeper navigation level. A tab defined under
navigation.secondaryTabs stays in its parent's tab bar until it is dragged
into a navigation group. The designer then adds its surface with
"placement": "navigation" and renders it only in the tree. Choosing
As tabs removes that tree placement and returns it to its parent tab bar.
For a new parent surface, use + Tabs on its navigation row, then drop a
record type or an existing entity-backed navigation item into that parent's
tab area.
Settings also provides a Navigation designer over the same JSON draft:
- Add groups without editing JSON.
- The record type and secondary tab panels show only unassigned items; drag an item into a group to expose it in navigation.
- Use + Tabs on a navigation parent and drop children into its tab area to create deeper navigation without editing JSON.
- Drag items between groups and drag group headers to reorder navigation.
- Choose registered Obsidian icons for groups and menu items with searchable previews.
- Move secondary child items back to their parent tab bar with As tabs.
- Remove an individual tab with the
xon its tab chip. If that child already exists in navigation, it becomes a primary item; otherwise an entity-backed tab returns to the unassigned record type pool. - Legacy secondary or setup items copied into configured navigation are normalized to primary items when they are not owned by an active tab area.
- Remove other navigation items with their Remove button or the drop-to-remove area; they return to the applicable unassigned panel.
The designer updates the workspace.json editor draft. Click Save and
apply to persist and activate its arrangement.
Settings also provides a Workbook export groups designer over
workspace.json.workbookGroups:
- Add, rename, remove and reorder reusable XLSX export bundles.
- Assign any runtime record type, including schema-derived types not placed in navigation.
- Include one record type in multiple bundles when exports overlap.
As with navigation, these edits update the JSON draft until Save and apply persists them.
Template Folders¶
BOB Workspace ignores entity files located inside folder segments named Templates or templates.
This lets starter vaults ship valid example/template notes with normal frontmatter, such as type: meeting, without those files appearing in dashboards, entity lists, workbook exports, or counts. Prefer putting reusable templates under paths such as {vault.templates}/... instead of relying on filename filters like !file.name.contains("Template").
Entity Identity¶
Each entity type normally needs:
- A frontmatter
typevalue, such asperson,deal,invoice, ortask. - A folder or folder set where records usually live.
- A field list.
- Optional Base views.
Filename-backed system records are an intentional exception; for example,
skill records resolve SKILL.md files and do not require type_value.
The plugin can find entity files by:
typeFilter: matchtype: valuetypeFilters: match several frontmatter key-value pairsfolders: scan one or more foldersfolder: scan one fallback foldertypesFilter: match one of severaltypevalues
These filters are combined carefully:
foldersandtypesFilterare OR lists internally.- Different filter categories are AND-combined.
- For type-only entities such as People, avoid adding folder restrictions unless the model really requires them.
Schemas¶
Schema files are the canonical way to define BOB Workspace record types. With
schema support enabled, author {vault.schemas.source}/*.yaml through the
Settings Data model designer or carefully as YAML source. Treat generated
FileClasses, JSON Schemas and data-model documentation as downstream outputs.
Example:
entity: person
label: Person
plural: People
icon: users
type_value: person
location_pattern: {me.people} or {client.people}
key_fields:
- name
- person_category
- company
- role
fields:
- name: type
type: string
required: true
- name: name
type: string
required: true
- name: person_category
type: string
required: false
enum:
- employee
- freelancer
- contractor
- business-contact
- personal-contact
- prospect
- other
- name: status
type: string
enum:
- active
- archived
default: active
field_aliases:
name:
- full name
- displayName
When schemas are enabled, BOB Workspace uses these fields to enrich built-in entities. That means workbook export/import, create forms, and entity tables can use the vault's field names instead of Cadence's original names.
location_pattern also participates in creation-time folder resolution. When a new record has enough filled values to satisfy a placeholder path, BOB Workspace creates the note under that resolved folder; otherwise it falls back to the entity's configured default folder prefix.
Entity note bodies can also be configured without code. Put a template object inside the schema's bob block to override the note frontmatter and body for that entity. Use frontmatter for default keys and body for the markdown body. Values support simple {{name}}, {{title}}, {{today}}, and similar placeholders.
For the built-in task-note mode, use workspace.json.templates.taskNote to define the same frontmatter and body structure.
Workspace-owned plugin settings also live under workspace.json.settings. That block carries the portable knobs that should travel with the vault, including schema enablement, Base mappings, navigation visibility, modules, task-note mode, task-note folders, workbook export folders, icon-driven workspace layout and entity folder configuration.
The Data model designer supports creating entity schema sources and editing
identity, icon, type value, location pattern, key fields, lifecycle values,
co-required relationships, discriminators, import field aliases, ordered
fields, field types, required flags, enum options, create defaults, display
hints and advanced BOB behavior. A save
writes a sibling .backup file before updating source YAML and reloads BOB
Workspace immediately. Save and regenerate validates all source schemas
and writes Metadata Menu FileClasses and JSON Schemas as derived outputs.
JSON Schema filenames follow type_value where present, matching frontmatter
identity; regeneration removes stale derived outputs after source rename or
archive.
Field default values populate new-record forms and newly created notes.
Use literal values for text, enum, number, boolean or list fields. Date fields
also accept {{today}}, resolved when a note is created. JSON Schema outputs
retain these defaults as derived documentation and validation metadata.
Bases¶
Base files should define how an entity is viewed.
Example:
filters: note.type == "person"
properties:
note.person_category:
displayName: Category
views:
- type: table
name: Business Contacts
filters:
and:
- note.person_category == "business-contact"
order:
- file.name
- person_category
- company
- role
BOB Workspace should treat Bases as the view source of truth where possible:
- selected view
- column order
- filters
- sort
- groupBy
- special Base view types
Base column order controls presentation only; it must not remove canonical schema fields from create or import workflows.
If the plugin cannot support a Base feature directly, it should show the unsupported filter/view information rather than silently pretending it is applied.
Derived Workspace Views¶
Some workspace screens are derived views over existing entities rather than separate entity types.
Teamis a People view overtype: person. By default it includesperson_categoryvaluesemployee,freelancer, andcontractor; the category set is configurable in plugin settings.Client Work > Workspacecombines meetings, communication threads, deliverables, feedback, surveys, testimonials, and decisions. Its client selector matches bothclient_idandend_client_id; its project selector matchesproject_id.Productivityreads tasks according to the configured task mode. Checkbox tasks come from daily note task sections. TaskNotes come from both the active TaskNotes folder and the TaskNotes archive folder so historical completion remains visible.
This keeps the vault model DRY: derived views should compose existing entity schemas instead of introducing duplicate entity types.
Field Aliases¶
Field aliases are the bridge between old Cadence names, spreadsheet headers, and canonical BOB field names.
Examples:
| Incoming name | Canonical field |
|---|---|
closeBy |
expected_close |
close date |
expected_close |
value |
deal_value |
lastContact |
last_contact |
phone number |
phone |
Define aliases in the canonical schema source. Keys are canonical fields and values are incoming spreadsheet column names accepted by CSV and workbook imports:
field_aliases:
deal_value:
- value
- amount
expected_close:
- closeBy
- close date
client_id:
- company
The Settings Data model designer edits this as one field per line, for
example expected_close: closeBy, close date. Field aliases must point to
defined schema fields and cannot conflict with another canonical field. The
legacy built-in importer synonyms remain as compatibility fallbacks.
Adding A New Entity¶
The no-code path uses four Settings designers in sequence. Each step saves automatically or through Save and apply; no JSON editing is required.
Step 1 — Define the schema¶
Open Settings → BOB Workspace → Data model and click + New schema.
Fill in the identity fields:
| Field | Purpose | Example |
|---|---|---|
| Entity key | Internal identifier, used in code and workspace.json | order |
| Label / Plural | Display name in the UI | Order / Orders |
| Type value | Value written to frontmatter type: |
order |
| Location pattern | Folder where notes are created | {client.root}/Orders/ |
| Icon | Lucide icon ID shown in navigation | package |
| Definition | Short description of the entity | A customer purchase order |
| Key fields | Fields shown in list headings | name, client_id, status |
| Lifecycle | Allowed values for a status field |
draft, open, fulfilled, cancelled |
Then add fields with + Add field. For each field:
- Set the field name (snake_case, e.g.
order_date) - Choose the data type (
Text,Number,Date,Enum, etc.) - For
Enumfields, add the allowed values in Options - Set a Default value for pre-populated forms; date fields accept
{{today}} - Mark Required for mandatory create-form fields
- Choose a BOB display override only when the JSON type differs from how
the UI should render the field (e.g. a
stringfield displayed ascurrency)
Click Save (triggered automatically on blur for text fields, or immediately for dropdowns, checkboxes, and buttons).
Click Save and regenerate to write downstream artifacts:
{vault.core}/FileClasses/{Entity}.md— Metadata Menu FileClass{vault.schemas}/json/{type_value}.json— JSON Schema for validation- Injects an entity table and definition block into
DATAMODEL.mdandDATAMODEL-FULL.mdbetween the<!-- BEGIN/END GENERATED -->markers
Step 2 — Place the entity in navigation¶
Open Settings → BOB Workspace → Workspace and go to the Navigation designer.
The Record types panel lists all schema-defined types that are not yet placed in navigation. Drag your new type into an existing group, or:
- Click + Group to create a new navigation group for it
- Set the group label and icon
- Drag the record type into the group
To create a secondary (inner) tab instead of a top-level item:
- Click + Tabs on the parent navigation row
- Drag the record type into that parent's tab area
Click Save and apply to persist the navigation arrangement to
workspace.json.
Step 3 — Create a Base (optional)¶
If the entity needs a custom view — filtered subsets, a specific column
order, or a non-table layout — create a .base file:
# {vault.bases}/Orders.base
filters: note.type == "order"
properties:
note.status:
displayName: Status
views:
- type: table
name: Open Orders
filters:
and:
- note.status == "open"
order:
- file.name
- client_id
- order_date
- status
Step 4 — Associate the Base¶
In Settings → BOB Workspace → Workspace, find the entity row and select
the .base file and view from the Base selector. The mapping is saved to
workspace.json.bases and travels with the vault.
Step 5 — Add to workbook export groups (optional)¶
Open the Workbook export groups designer in the same Workspace settings panel. Add the entity key to an existing group or create a new bundle.
Step 6 — Test¶
| Action | What to verify |
|---|---|
| Create record | Form shows correct fields and defaults; file lands in the right folder |
| List view | Records appear; columns match Base column order |
| Inline edit | Field saves via frontmatter |
| Base view | Filters apply; switching views works |
| CSV import | Column headers map to canonical field names (or aliases) |
| Workbook export | Entity appears in the correct XLSX sheet |
| Workbook import | Imported rows resolve field aliases and write correct frontmatter |
Gotchas (field-tested)¶
These cost real debugging time when building a standalone product vault on BOB Workspace:
- Schema field types are limited to
string | number | integer | boolean | array. There is nodatetype — dates are stored asstring(e.g.due_date,target_date,created). A schema withtype: datefails to load withCannot load <file>: Field "<x>" has unsupported type "date". - A nav entity must be defined in the schema layer, not only in
entities.json. An entity that exists only as anentities.jsonoverride (no canonical schema) is shown as[incompatible]in Settings — becauseentities.jsonis for overrides/special cases, while canonical entities live inSchemas/*.yamlwithschemas.enabled: true. - An entity nav item with no wired Base renders "coming soon". Add the mapping under the top-level
basesblock (bases: { <entity>: { file, view } }) so the item renders its Base. Without it the surface is just a placeholder. - Navigation is entity-backed. A view that is not an entity (e.g. a habit tracker reading
habit_*fields across daily notes) cannot be a navigation item. Surface it as a Base opened from the file tree, embedded in a note, or as a secondary tab — not a nav entry. - Settings live in
workspace.json → settings, notdata.json.taskMode,taskNotesFolder,folderProjects,dailyNoteFolder, andmodulesare persisted in the workspace definition; unknown keys placed indata.jsonare stripped on load. - (Obsidian, not BOB) Embedding a Base view inline uses a
```basecode block; `` does not render the view.
What Requires Code¶
Most entity and field changes should not require code. Code is only needed for:
- specialized non-generic surfaces
- custom non-table UI behavior
- new field widget types
- new import/export formats
- deeper Base feature support
- new commands/settings
If a change is just "this entity has a different field", it should be a schema change, not a plugin code change.
Current Maintenance Notes¶
The plugin still contains some legacy Cadence fallback fields. This is acceptable as compatibility fallback, but BOB vaults should rely on schemas/Bases for canonical behavior.
Known cleanup direction:
- Reduce built-in entity definitions to minimal fallbacks.
- Prefer schema-derived field definitions for import/export.
- Keep Base view support DRY rather than duplicating special views in plugin code.
- Document unsupported Base features clearly in the UI.