Thread YAML Schema Reference
Every Thread file conforms to this schema. loom check validates against it and rejects unknown keys (strict mode), so typos like requried: true fail loudly.
Top-level fields
| Key | Type | Required | Notes |
|---|---|---|---|
model |
string (PascalCase) | yes | Becomes the Go struct name |
label |
string | yes | Human-readable name; appears in UI / logs |
icon |
string | yes | Lucide icon name |
description |
string | no | Free-form |
module |
string | no | Module grouping (informational) |
color |
string | no | Hex or named colour (informational) |
fields |
array | yes | At least one entry |
relationships |
array | no | Cross-Thread links |
permissions |
array | no | Without these, only --no-auth mode reaches the model |
list_view |
object | no | Customise the generated list page |
form_view |
object | no | Customise the generated form |
hooks |
object | no | Lifecycle hook function names |
fields[] entry
| Key | Type | Required | Notes |
|---|---|---|---|
name |
string (snake_case) | yes | Becomes column + Go field name |
type |
enum | yes | One of 20 field types (see Field Type Catalogue) |
label |
string | no | UI label (defaults to title-cased name) |
required |
bool | no | NOT NULL in SQL; validate:"required" in Go |
unique |
bool | no | UNIQUE INDEX in SQL |
searchable |
bool | no | Adds a secondary INDEX in SQL |
default |
any | no | DEFAULT clause in SQL; type matches type |
options |
array | required for select/radio/multiselect |
List of {label, value, color?} |
target |
string | required for link |
PascalCase model name of the linked Thread |
min / max |
number | no | Validation constraints; render as validate:"min=N,max=N" |
placeholder |
string | no | UI hint |
help_text |
string | no | UI tooltip |
relationships[] entry
| Key | Values | Notes |
|---|---|---|
type |
has_many | belongs_to | many_to_many |
|
target |
PascalCase model name | required |
foreign_key |
string | for has_many / belongs_to |
through |
PascalCase model name | required for many_to_many (the join model) |
label |
string | UI label for the related-list panel |
permissions[] entry
| Key | Values | Notes |
|---|---|---|
role |
string | Role name; Public is the low-water-mark role |
can |
all | [read, create, update, delete] |
Either form is accepted |
list_view object
| Key | Type | Notes |
|---|---|---|
columns |
[field_name] |
Which fields show in the list table |
default_sort |
field_name |
|
sort_order |
asc | desc |
|
filters |
[field_name] |
Adds filter UI for these fields |
search |
bool | Adds a search bar |
page_size |
int | Default 20 |
form_view object
form_view:
sections:
- label: Details
columns: 2 # 1 or 2
fields: [number, customer_id, amount, status]
- label: Notes
columns: 1
fields: [notes]
related_lists:
- model: InvoiceItem
columns: [description, qty, price]
hooks object
hooks:
before_save: NormalizeInvoiceNumber
after_save: NotifyCustomer
before_delete: ConfirmNoDeps
after_delete: AuditDelete
on_submit: AdvanceWorkflow
Each value must be a valid Go function identifier; the validator rejects names like 4xPrice or do-it.
Validation guarantees
loom check enforces, among other rules:
- Every
fields[].nameis unique within the Thread - Every
list_view.columnsandform_view.sections.fieldsreference an actual field relationships.type = many_to_manyrequiresthroughselect/radio/multiselectrequire non-emptyoptionslinkrequirestarget
The full rule set is in Reference → Validation Error Catalogue.
See also
- Reference → Field Type Catalogue — every field type's Go and SQL mappings
- Concepts → Threads — what a Thread is for
Was this article helpful?