payloadcms / payloadcms/payload

Conditional non-data layout fields do not make nested required fields optional in generated types

Open
#17,896 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: typescript
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
  1. Add the collection above to a Payload config.
  2. Run payload generate:types.
  3. Inspect the generated Post interface.
  4. Observe that headline is 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:

  • row
  • collapsible
  • unnamed group
  • a tabs container
  • 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.