payloadcms / payloadcms/payload
Conditional non-data layout fields do not make nested required fields optional in generated types
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
When a non-data layout field has an admin.condition, nested required fields remain required in the generated TypeScript interface even though the parent condition can make those fields absent.
For example:
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'showHeadline',
type: 'checkbox',
defaultValue: false,
},
{
type: 'row',
admin: {
condition: (data) => data.showHeadline,
},
fields: [
{
name: 'headline',
type: 'text',
required: true,
},
],
},
],
}
The generated interface currently contains:
headline: string;
However, when showHeadline is false, Payload propagates the failed row condition into field traversal and skips validation for headline. A document can therefore omit it. The generated interface should represent that possibility:
headline?: string | null;
#17063 and #16954 fixed conditional row rendering in the admin UI, but they did not change type generation.
Reproduction Steps
- Add the collection above to a Payload config.
- Run
payload generate:types. - Inspect the generated
Postinterface. - Observe that
headlineis required even though the row condition can be false and server-side validation then permits it to be absent.
I also reproduced the same required-field output with conditions on:
rowcollapsible- unnamed
group - a
tabscontainer - an unnamed tab
A named tab with its own condition behaves correctly because it remains represented as a data-affecting tab field after flattening.
Expected Behavior
Required fields nested below a conditional non-data layout field should be optional in generated types.
The nested field can remain required within a data-bearing container when that container is present. For example, a conditional named tab should be optional, while its required children can remain required inside the tab object.
Root Cause
flattenAllFields() removes non-data layout fields such as rows and collapsibles and returns their nested fields without preserving whether a parent had an admin.condition.
fieldsToJSONSchema() then calls fieldIsRequired() on the flattened child. Since only the child's own condition is visible, a required child is added to the JSON Schema required array.
Workaround
Copy or compose the layout condition onto every immediate child:
const condition = (data) => data.showHeadline
{
type: 'row',
admin: { condition },
fields: fields.map((field) => ({
...field,
admin: {
...field.admin,
condition: (data, siblingData, context) =>
condition(data, siblingData, context) &&
(field.admin?.condition
? field.admin.condition(data, siblingData, context)
: true),
},
})),
}
This keeps the generated child fields optional, but duplicates a condition that should be inherited from the layout container.
Suggested Fix
Preserve inherited conditionality while flattening non-data layout fields, then take that inherited state into account in fieldIsRequired() / fieldsToJSONSchema().
Only a boolean "has conditional parent" state is needed for type generation; the condition callback should not need to be copied or executed.
A focused JSON Schema regression test could cover row, collapsible, unnamed group/tab, and a named-tab control case.
Which area(s) are affected?
area: typescript
Environment Info
Payload: current main as of 2026-08-23; also reproduced with 3.87.0
Node: 26.7.0
Operating System: macOS
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with flattenAllFields(), then follow how fieldsToJSONSchema() and fieldIsRequired() process flattened children during payload generate:types. Add regression coverage for conditional row, collapsible, unnamed group/tab, and named-tab cases using the JSON Schema generation path. Done means children under conditional non-data layouts are optional, while required children in conditional named-tab data remain required within that container.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- developer-experience
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100