Summary
The storage layer already handles nested user-schema objects — CreateAttributesFromMap writes
dotted EAV keys (address.street) and honours x-unique on nested leaves, and
docs/design/flowengine/user-schema.md:125 documents this as supported. The flow engine's field
resolver sees only top-level scalars, so a nested property cannot be collected by any spelling.
Repro
User schema (accepted today by POST /schemas):
{
"type": "object",
"required": ["email", "address"],
"properties": {
"email": { "type": "string", "format": "email" },
"address": {
"type": "object",
"required": ["street"],
"properties": { "street": { "type": "string" } }
}
}
}
Every possible flow definition for it:
fields |
Result |
["email", "address.street"] |
rejected: flow field: not a property in the user schema: "address.street" |
["email", "address"] |
saves, renders a text box, then fails at the last step of registration with a non-field-specific ErrUserInvalid |
["email"] |
rejected: required fields [address] in user schema are missing in the flow definition steps |
There is no fourth option. The middle row is the worst: a definition-time type error deferred into
a live signup, after the user has already entered credentials.
Root cause
Three places, all in the resolver:
flow_field_resolver_schema.go:86-90 — exact properties[field] lookup, no path walk.
flow_field_resolver_schema.go:148-168 — no object/array case, so they fall through to
FlowFieldTypeText.
flow_definition_validator.go:118,182-204 — required coverage compares the schema's root
required array against whole field names.
packages/config/src/validate.ts mirrors this validator for zitadel plan and carries the same
three defects.
Expected
fields: ["address.street"] resolves to a scalar field, with the leaf's own validation
keywords and x-unique.
- Object- and array-typed properties are rejected at save time, never rendered as text.
- Required coverage walks nested
required; a leaf is required iff every ancestor is too.
- Submitted flat keys un-flatten into the nested document the schema validates.
Summary
The storage layer already handles nested user-schema objects —
CreateAttributesFromMapwritesdotted EAV keys (
address.street) and honoursx-uniqueon nested leaves, anddocs/design/flowengine/user-schema.md:125documents this as supported. The flow engine's fieldresolver sees only top-level scalars, so a nested property cannot be collected by any spelling.
Repro
User schema (accepted today by
POST /schemas):{ "type": "object", "required": ["email", "address"], "properties": { "email": { "type": "string", "format": "email" }, "address": { "type": "object", "required": ["street"], "properties": { "street": { "type": "string" } } } } }Every possible flow definition for it:
fields["email", "address.street"]flow field: not a property in the user schema: "address.street"["email", "address"]ErrUserInvalid["email"]required fields [address] in user schema are missing in the flow definition stepsThere is no fourth option. The middle row is the worst: a definition-time type error deferred into
a live signup, after the user has already entered credentials.
Root cause
Three places, all in the resolver:
flow_field_resolver_schema.go:86-90— exactproperties[field]lookup, no path walk.flow_field_resolver_schema.go:148-168— noobject/arraycase, so they fall through toFlowFieldTypeText.flow_definition_validator.go:118,182-204— required coverage compares the schema's rootrequiredarray against whole field names.packages/config/src/validate.tsmirrors this validator forzitadel planand carries the samethree defects.
Expected
fields: ["address.street"]resolves to a scalar field, with the leaf's own validationkeywords and
x-unique.required; a leaf is required iff every ancestor is too.