Relation Field Validation#
Relation inputs in the Content Manager use a nested { connect: [...], disconnect: [...] } value structure. This structure creates a path mismatch between where validation errors are stored and where value updates are applied, causing validation errors on required relation fields to persist even after the user fills the field — until the next save or publish action reruns validation.
Active issue: #27248 (filed 2026-08-05)
The Path Mismatch#
When a required relation field (e.g., author) fails validation, the error is stored in formState.errors at the key author. But when a user connects or disconnects a relation, the write goes to a nested path:
- Connect:
onChangeRelationField("author.connect", [...]) - Disconnect:
addFieldRow("author.disconnect", {...})/removeFieldRow("author.connect", index)
The shared Form reducer dispatches SET_FIELD_VALUE, ADD_FIELD_ROW, or REMOVE_FIELD_ROW for these operations — and none of those reducer cases touch draft.errors . The error at author therefore remains until validation reruns.
Errors are only cleared when validate() is called, which starts with setErrors({}) . That happens on save or publish — not on field change. There is no relation-specific error clearing in Relations.tsx when handleConnect or handleDisconnect are called .
Form Error Architecture#
Form.tsx maintains errors as a nested object (formState.errors) that mirrors the values shape. useField(path) reads getIn(state.errors, path) — so a field registered as author reads errors at exactly "author", not at "author.connect" or "author.disconnect".
Errors are only written via the SET_ERRORS action , exposed to callers as setErrors(). No value-mutation action (SET_FIELD_VALUE, ADD_FIELD_ROW, REMOVE_FIELD_ROW) clears or updates any error key . This is by design for regular scalar fields — typing into a text input doesn't auto-clear the error — but it breaks down for relation fields whose value updates land on a child path rather than the error's parent path.
Related Fix: Content-Type Builder (PR #27222)#
An identical stale-error pattern existed in the Content-Type Builder's FormModal/reducer.ts: formErrors had a single writer (setErrors) and was only cleared on submit. Actions that replaced form data — setAttributeDataSchema, setCustomFieldDataSchema, setDynamicZoneDataSchema, setDataToEdit — left formErrors untouched, so errors from a discarded type persisted on the blank form for the next type.
PR #27222 (merged 2026-08-05) fixed this by resetting formErrors = {} inside each of those four action handlers, matching the behavior of resetPropsAndSetTheFormForAddingACompoToADz which already returned to initialState. This PR is the reference implementation for clearing stale errors when form data is replaced.
The Content Manager's Relations.tsx has no equivalent fix as of this writing — the path-mismatch issue in issue #27248 remains open.
Key Files#
| File | What to look at |
|---|---|
packages/core/admin/admin/src/components/Form.tsx | Form reducer, SET_FIELD_VALUE / ADD_FIELD_ROW / REMOVE_FIELD_ROW (no error clearing); validate() → setErrors({}) |
packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations/Relations.tsx | handleConnect writes to fieldName.connect; useHandleDisconnect writes to fieldName.connect/fieldName.disconnect — no error clearing on either path |
packages/core/content-type-builder/admin/src/components/FormModal/reducer.ts | Reference fix: resets formErrors inside data-replacing action handlers |
Tracked issues / PRs:
- Issue #27248 — Content Manager: persistent validation error on required relation field (open)
- Issue #20947 — Content-Type Builder: stale error on type switch → fixed by PR #27222