windmill-labs/windmill · error

Flow modules must be an array

Error message

Flow modules must be an array

What it means

validateFlowModules in editableFlowJson.ts is the shared validator for a flow's modules payload. Before schema-checking, it asserts the raw modules value is an array; anything else (object, string, null, undefined) throws this error. It exists to give callers an early, clear message instead of an opaque Zod type error.

Source

Thrown at frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts:217

			(Object.keys(jsonSchema).length === 1 && jsonSchema.$schema)
		) {
			return null
		}
		const formatted = formatJsonSchemaForError(jsonSchema)
		if (formatted && formatted !== 'unknown' && !formatted.startsWith('{')) return formatted
		if (formatted && formatted.startsWith('{') && formatted !== '{ }') return formatted
	} catch {
		// Ignore errors from toJSONSchema
	}
	return null
}

export function validateFlowModules(
	rawModules: unknown,
	ctx: FlowValidationContext = {}
): FlowModule[] {
	if (!Array.isArray(rawModules)) {
		throw new Error('Flow modules must be an array')
	}

	const parsedModules = rawModules as FlowModule[]
	const modulesSchema = ctx.modulesSchema ?? flowModulesSchema
	const result = modulesSchema.safeParse(parsedModules)
	if (!result.success) {
		const errors = result.error.issues.slice(0, 5).map((e) => {
			const path = e.path
			const moduleIndex = typeof path[0] === 'number' ? path[0] : undefined
			const moduleId = moduleIndex !== undefined ? parsedModules[moduleIndex]?.id : undefined
			const fieldPath = path.slice(1).join('.')

			let message = e.message
			if (e.code === 'invalid_type') {
				const targetSchema = getSchemaAtPath(
					modulesSchema,
					path as (string | number)[],
					parsedModules

View on GitHub (pinned to e474e8803c)

Solutions

  1. Pass modules as a JSON array of module objects: [{ id, value, ... }, ...].
  2. If the payload is a JSON string, parse it with JSON.parse before validating.
  3. If modules are keyed by id, convert the object to an array with Object.values().

Example fix

// before
validateFlowJson({ modules: { a: { id: 'a' } } })
// after
validateFlowJson({ modules: [{ id: 'a', value: {...} }] })
Defensive patterns

Strategy: type-guard

Validate before calling

if (!Array.isArray(payload.modules)) throw new Error('modules must be an array of module objects')

Type guard

function isModuleArray(v: unknown): v is FlowModule[] {
  return Array.isArray(v) && v.every((m) => !!m && typeof m === 'object' && typeof (m as any).id === 'string')
}

Try / catch

try {
  validateFlowModules(rawModules)
} catch (e) {
  if (e.message === 'Flow modules must be an array') {
    rawModules = Array.isArray(rawModules) ? rawModules : Object.values(rawModules ?? {})
  } else throw e
}

Prevention

When it happens

Trigger: Calling validateFlowModules (directly or via flowTools 'modules' handling / validateEditableFlowJson) with a modules value that is not an array — e.g. an object keyed by module id, null, or a JSON string of the array.

Common situations: A model emits modules as a map ({"id": {...}}) instead of a list; the caller forgets to JSON.parse a string payload; a partially built object is passed where an array was expected.

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/68bbf0681ddddfd5. Report an issue: GitHub.