Intermediate May 3, 2026 · 2 min read

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[].name is unique within the Thread
  • Every list_view.columns and form_view.sections.fields reference an actual field
  • relationships.type = many_to_many requires through
  • select / radio / multiselect require non-empty options
  • link requires target

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?