windmill-labs/windmill · error

'value.modules' must be a list

Error message

'value.modules' must be a list

What it means

validateShape in FlowYamlEditor.svelte checks that the parsed OpenFlow document's 'value.modules' key is an array, since a Windmill flow body is a list of modules. It throws when 'value.modules' is absent, a scalar, or a non-array object — meaning the flow body shape is wrong even though the document has a proper 'value' mapping.

Source

Thrown at frontend/src/lib/components/flows/header/FlowYamlEditor.svelte:47

		code = YAML.stringify(filteredContentForExport(flowStore.val))
		initialCode = code
		editor?.setCode(code)
	}

	/** `value` is assigned unconditionally below and `FlowEditor` dereferences
	 * `flowStore.val.value.modules`, so an OpenFlow document missing it takes the
	 * whole editor down mid-render — after the toast has already claimed success.
	 * Reject it up front instead. */
	function validateShape(parsed: unknown) {
		if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
			throw new Error('the document must be a mapping (key: value)')
		}
		const value = (parsed as Record<string, unknown>).value
		if (typeof value !== 'object' || value === null || Array.isArray(value)) {
			throw new Error("missing 'value' - paste a whole OpenFlow document, not just its value")
		}
		if (!Array.isArray((value as Record<string, unknown>).modules)) {
			throw new Error("'value.modules' must be a list")
		}
	}

	function validateGroups(groups: { start_id: string; end_id: string }[] | undefined) {
		if (!groups) return
		const seen = new Set<string>()
		for (const g of groups) {
			const key = `${g.start_id}:${g.end_id}`
			if (seen.has(key)) {
				throw new Error(`Duplicate group: '${g.start_id}' → '${g.end_id}'`)
			}
			seen.add(key)
		}
	}

	function apply() {
		try {
			const parsed = YAML.parse(code)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Ensure 'value.modules' exists and is a YAML list, with each module prefixed by '- id: ...'
  2. If you intended a single step, wrap it in a one-element list
  3. Check the key spelling is exactly 'modules' and it is nested directly under 'value'
  4. Validate against an exported flow's YAML to confirm the expected structure

Example fix

// before (modules as single object)
value:
  modules:
    id: a
    value:
      type: rawscript
// after (modules as a list)
value:
  modules:
    - id: a
      value:
        type: rawscript
Defensive patterns

Strategy: validation

Validate before calling

const parsed = yaml.load(text)
if (!Array.isArray(parsed?.value?.modules)) {
  alert('value.modules must be a YAML list of module objects')
}

Type guard

function hasModulesList(v: unknown): v is { value: { modules: unknown[] } } {
  return typeof v === 'object' && v !== null && Array.isArray((v as any).value?.modules)
}

Try / catch

try {
  editor.apply(yamlText)
} catch (e) {
  if (e.message.includes("'value.modules' must be a list")) {
    sendUserToast('Each module must be a list item: prefix with "- "', true)
  }
}

Prevention

When it happens

Trigger: Calling apply() → validateShape() with YAML where value.modules is missing, set to a single module object instead of a list, misspelled (e.g. 'value.Modules' or 'value.steps'), or a mapping of module-id → module.

Common situations: Editing YAML by hand and dropping the list syntax (a dash) so modules becomes a single object; porting from another flow format that uses a different key name for steps; accidentally deleting the modules list during a merge.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/c51b456b9f07a522. Report an issue: GitHub.