Skip to contents

1. Copy the example

Download the survey YAML and save it as my-form.yaml.

schema_version: 1.0.0
form:
  id: survey
  version: '2026.2'
references:
  code_lists:
    - observers
    - stations
  registries:
    - contacts
sheets:
  # Fixed cells with an exact physical sheet size.
  Metadata:
    layout: {kind: cells, column_count: 3, row_count: 5}
    fields:
      submission: {mandatory: true, location: C2, type: string, pattern: '^[0-9]{3}$'}
      date: {mandatory: true, location: C3, type: date}
      contact_email: {location: C4, type: email, registry: {id: contacts, column: email, severity: warning}}
      comment: {location: C5, type: string}
  # Repeated rows; field declaration order determines column order.
  Observations:
    layout: {kind: table, start_row: 6}
    primary_key: [station, observation_id]
    fields:
      station: {mandatory: true, type: string, code_list: stations}
      count: {mandatory: true, type: integer, minimum: 0, maximum: 1000}
      length: {type: number, minimum: 0}
      unit: {type: string, required_field: length, enum: [cm, m]}
      active: {mandatory: true, type: boolean}
      observed_at: {type: datetime}
      latitude: {type: latitude}
      longitude: {type: longitude}
      observation_id: {type: string, pattern: '^OBS-[0-9]{3}$'}
      quality: {type: integer, enum: [1, 2, 3]}
      observer: {type: string, code_list: {id: observers, severity: warning}}
  Samples:
    layout: {kind: table, start_row: 6}
    primary_key: [station, observation_id, sample_no]
    parent_key: {sheet: Observations, fields: [station, observation_id]}
    fields:
      station: {mandatory: true, type: string}
      observation_id: {mandatory: true, type: string}
      sample_no: {mandatory: true, type: integer, sequence: {start: 1, group_by: [station, observation_id]}}
      minimum_length: {type: number, minimum: 0, maximum_field: maximum_length}
      maximum_length: {type: number, minimum: 0}
      unit: {type: string, required_field: minimum_length, enum: [cm, m]}
  Measurements:
    layout: {kind: table, start_row: 6}
    primary_key: [station, observation_id, sample_no, reading_no]
    parent_key: {sheet: Samples, fields: [station, observation_id, sample_no]}
    fields:
      station: {mandatory: true, type: string}
      observation_id: {mandatory: true, type: string}
      sample_no: {mandatory: true, type: integer}
      reading_no: {mandatory: true, type: integer, sequence: {start: 1, group_by: [station, observation_id, sample_no]}}
      value: {mandatory: true, type: number, minimum: 0}
  Equipment:
    layout: {kind: table, start_row: 6}
    primary_key: [equipment_id]
    fields:
      equipment_id: {mandatory: true, type: string}
      serial: {mandatory: true, type: string, unique: true}
      label: {mandatory: true, type: string}

2. Edit your fields

For example, a required count and an optional length with its unit:

count: {mandatory: true, type: integer, minimum: 0}
length: {type: number, minimum: 0}
unit: {type: string, required_field: length, enum: [cm, m]}

For table sheets, field order is column order. The column count is derived from the fields; omit column_count.

3. Check your model

form.model::read_form_model("my-form.yaml")

A valid file returns a model summary; an invalid file reports the problem.

See Model specification for field types, constraints and layouts.