windmill-labs/windmill · error
Flow JSON must be an object
Error message
Flow JSON must be an object
What it means
validateEditableFlowJson is the entry point for the copilot's flow JSON representation; it first requires the input to be a non-null, non-array object. Anything else — null, undefined, a bare array, a string, a number — cannot represent a flow and is rejected immediately before any field-level checks.
Source
Thrown at frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts:340
'modules',
'schema',
'preprocessor_module',
'failure_module',
'groups',
'notes'
] as const
/**
* Parse and validate a raw object as an `EditableFlowJson`. Validates module
* shape, schema shape, optional special modules (with their reserved ids),
* groups, top-level flow settings, and that no module ids collide.
*/
export function validateEditableFlowJson(
rawFlow: unknown,
ctx: FlowValidationContext = {}
): EditableFlowJson {
if (!rawFlow || typeof rawFlow !== 'object' || Array.isArray(rawFlow)) {
throw new Error('Flow JSON must be an object')
}
const flow = rawFlow as Record<string, unknown>
// Reject unknown top-level keys: silently dropping them would make patch
// tools report success for edits that never land on the flow.
const allowedKeys = new Set<string>([
...EDITABLE_FLOW_STRUCTURAL_KEYS,
...FLOW_VALUE_SETTINGS_KEYS
])
const unknownKeys = Object.keys(flow).filter((key) => !allowedKeys.has(key))
if (unknownKeys.length > 0) {
throw new Error(
`Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Allowed keys: ${[...allowedKeys].join(', ')}`
)
}
const settingsResult = flowValueSettingsSchema.safeParse(flow)View on GitHub (pinned to e474e8803c)
Solutions
- Wrap the payload in an object: pass {"modules": [...], ...} rather than the modules array itself.
- JSON.parse the input first if it is a string.
- Handle null/undefined upstream and skip validation for a genuinely absent flow.
Example fix
// before
validateEditableFlowJson([{id:'m1',value:{...}}])
// after
validateEditableFlowJson({modules:[{id:'m1',value:{...}}]}) Defensive patterns
Strategy: type-guard
Validate before calling
function assertFlowObject(raw) {
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
throw new TypeError('Flow JSON must be a non-array object')
return raw
} Type guard
function isEditableFlowJson(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v) &&
Array.isArray(v.modules)
} Try / catch
try {
const flow = validateEditableFlowJson(raw)
} catch (e) {
if (String(e.message) === 'Flow JSON must be an object') {
// wrap arrays in {modules: ...} or JSON.parse strings before retrying
} else throw e
} Prevention
- Ensure tool payloads are objects with a modules array, never bare arrays
- JSON.parse string inputs before validation
- Check for null upstream results before calling validation
When it happens
Trigger: Calling validateEditableFlowJson (via flowTools parsedFlow/patch tools) with null, undefined, an array of modules, or a JSON string instead of an object with `modules` etc.
Common situations: An LLM emits a bare array of modules instead of an object; JSON.parse produced a string; an upstream function returned null for a missing flow.
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 agent modules ${providerless.map((id) => `"${id}"`).join(
- Flow schema must be an object or null
- Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Al
- {webhook_key} must be a URL string, got {kind}
- result.substring(__RESULT_ERR_PREFIX.length)
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/4987d2445eb9d934.
Report an issue: GitHub.