Skill v1.0.1
currentAutomated scan100/100+7 new
version: "1.0.1" name: binder-modeling description: Binder data modeling — define entity types, fields, relations, constraints, views, and navigation. Use when asked to "create a type", "add a field", "define a schema", "set up relations", "model entities", "create a view", "set up navigation", "render entities as files", or design a binder workspace schema.
Binder Data Modeling
Schema is stored as data — types and fields are config entities that evolve through transactions.
Core Concepts
- Field — first-class schema element (name, data type, constraints). Shared across types (RDF-style)
- Type — organizes entities and applies field constraints. Any entity can have any field
- Transaction — atomic changes to multiple entities. Schema changes use
binder tx import
Namespaces
- Record — user data. References use UIDs
- Config — schema (Fields, Types, Attributes). References use immutable keys
- Transaction — append-only audit log
Defining Fields
Fields exist independently of types. Define in a transaction YAML and import with binder tx import:
- author: systemconfigs:- key: prioritytype: Fieldname: PrioritydataType: optionoptions: [low, medium, high]default: medium- key: assigneetype: Fieldname: AssigneedataType: relationrange: [User]
Defining Types
Types compose fields with optional per-type constraints:
- author: systemconfigs:- key: Tasktype: Typename: Taskfields:- title: { required: true }- status: { default: active, only: [draft, active, complete] }- priority- parent: { only: [Task] }- children: { only: [Task] }
Constraints: required, default, only: [...], exclude: [...].
Relations
Use inverseOf for bidirectional sync. See references/relations.md for patterns and storage details.
1:M — allowMultiple side declares inverseOf, single-value side stores data:
- key: parenttype: FielddataType: relation- key: childrentype: FielddataType: relationallowMultiple: trueinverseOf: parent
M:M — both sides allowMultiple, both store data:
- key: relatesTotype: FielddataType: relationallowMultiple: trueinverseOf: relatesTo
Attributes
Attach metadata to field values (e.g. role on an assignedTo relation). An attribute is just a regular Field that another field references via its attributes list. See references/field-attributes.md for full details.
- author: systemconfigs:- key: roletype: FielddataType: plaintext- key: assignedTotype: FielddataType: relationrange: [User]attributes: [role]
Note:docs/concepts/field-attribute.mdshowstype: Attributein examples — that form is rejected by the runtime. Usetype: Fieldfor the attribute definition itself.
Rendering: Views and Navigation
A schema describes data shape. Two more config entity types — View and Navigation — describe how that data is rendered as files and edited back into entities. They live in the same config namespace as Field and Type and are imported the same way (binder tx import).
- View — markdown template with
{fieldName}slots. Renders an entity to markdown; edits to the rendered file extract back into a transaction - Navigation — maps a query to a file path and a view, producing the workspace's file tree
Minimal view:
- author: systemconfigs:- key: task-viewtype: Viewpreamble: [key, status, priority, parent]viewContent: |# {title}{description}## Details{details}
Minimal navigation wiring that view to a file path:
- author: systemconfigs:- key: nav-taskstype: Navigationpath: tasks/{key}where: { type: Task }view: task-view
Result: every Task entity renders to tasks/{key}.md using task-view. Editing the file syncs changes back to the entity.
Key points to know when modeling:
- Path placeholders are single-segment only (cannot contain
/); fan-out happens automatically onallowMultiplepath fields - Path without a
viewproduces a YAML file; path ending with/produces a directory for children - Items with a
whereclause support create-by-file: dropping a new file at a matching path creates the entity - When a sub-view renders new relation children, the type must be resolvable: render a
{type}slot, narrow the field'srangeto one type, or setonly: SingleTypeon the parent type's field
See references/views.md for slot syntax, expression props (view:, where:, flags), formats, and bidirectional sync. See references/navigation.md for path interpolation, fan-out, nested children, and the rendering pipeline.
Data Types
Canonical list comes from binder schema -n config — inspect the dataType field's options to confirm in your build.
- Text —
plaintext,richtext - Numbers —
boolean,integer,decimal - Time —
date,datetime,period,interval,duration - References —
relation,uri,fileHash,image - Enumeration —
option(single value, or multi-value withallowMultiple: true) - Query —
query(stored query definition) - System (auto-managed, do not define manually) —
seqId,uid
Common pitfalls
- `text` and `string` are not valid. Use
plaintext(unformatted) orrichtext(markdown). Pair with a format constraint (see below) for tighter validation. - `optionSet` is internal. Used by binder's built-in
optionsconfig field. For user-facing multi-select enums, usedataType: optionwithallowMultiple: true:
```yaml
- key: qualityChecks
type: Field dataType: option allowMultiple: true options: [data, experience, question, angle, extension] ```
- `json`, `object` are used by built-in config fields but generally not needed for user data; prefer structured fields or relations.
Format Constraints
plaintext, richtext, and period accept format hints that restrict structure. The tighter the format, the more places a field can be reused in views and sync. See docs/concepts/text-format.md for the full rationale and per-format validation rules.
plaintextFormat:identifier|word|phrase|line|paragraph|uri|filepath|semverrichtextFormat:word|phrase|line|block|section|documentsectionrequiressectionDepth(1–5) to anchor heading levels (sectionDepth: 2means content lives under an##heading; only###and deeper allowed inside).periodFormat:day(YYYY-MM-DD) |week(YYYY-W##) |month(YYYY-MM) |quarter(YYYY-Q#) |year(YYYY). Required onperiodfields.
Multi-value delimiter (when allowMultiple: true) follows the format:
- comma:
identifier,word,phrase,semver - newline:
line,uri,filepath - blank line:
paragraph,block - header at sectionDepth+1:
section - horizontal rule (
---):document
Example:
- key: handletype: FielddataType: plaintextplaintextFormat: identifier- key: biotype: FielddataType: richtextrichtextFormat: block- key: dayPeriodtype: FielddataType: periodperiodFormat: day
Field Properties
Full list of properties accepted on a Field config entity:
| Property | Purpose | |
|---|---|---|
key, name, description | Identity and labelling | |
dataType | Required. See Data Types above | |
allowMultiple | Field stores an array of values | |
options | Required for dataType: option. List of { key, name, description? } (or just keys) | |
domain | Restrict which types can carry this field (relation field on Type) | |
range | Restrict relation targets to specific types | |
inverseOf | Bidirectional sync target. See Relations | |
unique | Enforce uniqueness across the namespace | |
default | Default value when field is unset | |
attributes | Other Fields whose values can annotate this field's values | |
plaintextFormat / richtextFormat / sectionDepth / periodFormat | Format constraints | |
when | Conditional applicability, e.g. when: { dataType: option } (used by built-in options field). Advanced — most schemas don't need it |
Built-in Record Fields
Defined in packages/repo/src/model/record-schema.ts. Reuse before defining custom equivalents.
Identity & labeling (core, all namespaces): id, uid, key, type, name, description, tags
Content (record only):
title—plaintext line, primary headingsummary—richtext block[], post-hoc short version of bodydetails—richtext sectionatsectionDepth: 2, structured bodycontent—richtext document, full body with own heading hierarchy
External:
url—uri, primary linkreferences—uri[], related external resourcessourceFiles—plaintext filepath[], source paths
Classification:
status—option, lifecycle. Options:draft,pending,active,paused,complete,cancelledpriority—option, importance. Options:p0(Blocker),p1(Urgent),p2(High),p3(Medium),p4(Low)source—plaintext line, provenance label for imported data
Temporal: dueDate (date), startDate (date), completedAt (datetime)
Structural relations (all relation data type):
- Hierarchy:
parent↔children - Order:
previous↔next - Containment:
partOf↔contains - Dependency:
requires↔requiredBy - Symmetric:
relatesTo↔relatesTo
System-managed (do not set, userReadonly: true): createdAt, updatedAt, createdBy, updatedBy. id and uid are also immutable identity fields you never set.
Attach a built-in to a type without redefining — just reference its key under fields:
- key: Tasktype: Typefields:- title: { required: true }- status: { default: pending, only: [pending, active, complete, cancelled] }- priority- parent: { only: [Task] }- children: { only: [Task] }
Enums and Constraints
Field definitions are global; per-type constraints live on the Type's fields list. Use them to restrict a shared field for one type without forking it.
| Constraint | Applies to | Use when | |
|---|---|---|---|
required: true | Any field | The type cannot exist without this field set | |
default: <value> | Any field with a default | Set initial value when omitted on create | |
only: [a, b, c] | option field | Subset the global enum for this type. E.g. global status has 6 values; restrict Task.status to a 3-value workflow | |
only: [TypeA, TypeB] | relation field | Restrict relation targets to specific types. E.g. Task.parent: { only: [Task, Milestone] } | |
exclude: [...] | option or relation field | Inverse of only — keep all but a few. Use only when the kept set is shorter, exclude when the rejected set is shorter | |
description: "..." | Any field | Per-type doc that overrides the field's general description | |
value: <value> | Any field | Pin a constant value for this type (advanced; rarely needed) |
Example using all four common constraints:
- key: Tasktype: Typefields:- title: { required: true }- status:default: pendingonly: [pending, active, complete, cancelled]- priority: { default: p3 }- parent: { only: [Task, Milestone] }
System Fields
Auto-managed, do not set on create or update: id, uid, createdAt, updatedAt, createdBy, updatedBy.
key and type are user-settable but constrained: key is unique within the namespace and validated against the identifier format; type is immutable once set.
Best Practices
- Run
binder schemaandbinder schema -n configbefore adding types or fields. Reuse the built-ins (see Built-in Record Fields above) —title,name,description,tags,status,priority,parent/children,partOf/contains,requires/requiredBy,relatesTo,dueDate, etc. — instead of duplicating. - Verify
dataTypeagainst the live schema before defining custom fields:
``bash binder schema -n config --format yaml | awk '/key: dataType/,/^ [a-z]/' | head -40 ``
- Reuse fields across types — don't duplicate.
- Use
onlyconstraints to restrict relation targets per type and to subset enum options per type. - Always define
inverseOffor bidirectional relations. - Preview with
binder tx import -dbefore applying — but do not trust dry-run for `dataType` validity. Dry-run is lenient about dataType values; an invaliddataTypemay pass dry-run and fail apply with an opaque "failed creating changeset" error and an empty log. If apply fails after a clean dry-run, suspect dataType first; bisect the transaction by halving configs to isolate the offender. - When in doubt, split schema changes into two transactions: fields first, then types that reference them.
- Avoid generic field names (
state,kind,type). Prefer names that say what the field means at the query site (publishStatus,format,qualityChecks). - Don't model entities for taxonomy you can express as enums or freeform tags. Promote a tag/pillar to its own type only when it needs its own fields (description, owner, history).