Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
35219c5
feat(payload): add isFieldDisabled helper for consolidated disabled prop
jacobsfletch May 5, 2026
ec7918e
chore(payload): re-export isFieldDisabled from shared barrel
jacobsfletch May 5, 2026
84d40d1
feat(payload): widen FieldAdmin.disabled to accept object form
jacobsfletch May 5, 2026
100d5de
refactor(ui): use isFieldDisabled helper in list-column paths
jacobsfletch May 5, 2026
2fffedb
refactor(ui): use isFieldDisabled helper in WhereBuilder paths
jacobsfletch May 5, 2026
ab680ad
refactor(ui): use isFieldDisabled helper in groupBy and bulk-edit paths
jacobsfletch May 5, 2026
139ae41
refactor(payload): migrate internal field literals to disabled object…
jacobsfletch May 5, 2026
7c01375
refactor(payload): UI-field auto-disable writes new disabled shape
jacobsfletch May 5, 2026
b9e492c
feat(payload)!: remove deprecated disable* field admin props
jacobsfletch May 5, 2026
4cc20a2
feat(codemod): add consolidate-disabled-fields transform
jacobsfletch May 5, 2026
e488288
test(codemod): cover merge, preserve-true, and non-matching cases
jacobsfletch May 5, 2026
8743fab
test: migrate fixtures to consolidated disabled field shape
jacobsfletch May 5, 2026
f4831df
docs: document consolidated field.admin.disabled prop
jacobsfletch May 5, 2026
7286042
feat!: consolidate admin.disabled properties
jacobsfletch May 5, 2026
bac7460
Merge branch 'main' into feat/consolidated-disabled-fields
jacobsfletch May 6, 2026
eae9f7f
fix build
jacobsfletch May 6, 2026
4457096
disbale id from bulk edit
jacobsfletch May 7, 2026
c8f53af
rename codemod
jacobsfletch May 7, 2026
8eb24f9
rename disabled.edit to disabled.field
jacobsfletch May 7, 2026
2064bb4
Merge branch 'main' into feat/consolidated-disabled-fields
jacobsfletch May 7, 2026
90c6c98
fix docs
jacobsfletch May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ packages/payload/*.js
packages/payload/*.d.ts
payload-types.ts
tsconfig.tsbuildinfo
packages/codemod/src/transforms/**/*.input.ts
packages/codemod/src/transforms/**/*.output.ts
2 changes: 1 addition & 1 deletion docs/fields/date.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ The `override` function allows you to customize the auto-generated timezone sele
...baseField,
admin: {
...baseField.admin,
disableListColumn: true, // Hide from list view columns
disabled: { column: true }, // Hide from list view columns
},
}),
},
Expand Down
72 changes: 56 additions & 16 deletions docs/fields/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -615,22 +615,62 @@ export const CollectionConfig: CollectionConfig = {

The following options are available:

| Option | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../custom-components/overview) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`position`** | Use `'sidebar'` to render the field in the sidebar or `'main'` (default) to keep it in the main area. |
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
| **`disableGroupBy`** | Set `disableGroupBy` to `true` to prevent fields from appearing in the list view groupBy options. Defaults to `false`. |
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. Defaults to `false`. |
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. Defaults to `false`. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
| Option | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../custom-components/overview) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`position`** | Use `'sidebar'` to render the field in the sidebar or `'main'` (default) to keep it in the main area. |
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| **`disabled`** | Controls where the field is disabled in the [Admin Panel](../admin/overview). Pass `true` to disable everywhere, or an object `{ field?, column?, filter?, groupBy?, bulkEdit? }` for granular per-area control. [More details](#disabled). |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |

### Disabled

The `admin.disabled` property controls where a field is disabled in the Admin Panel. It accepts either a boolean or an object.

#### Disable everywhere (boolean)

Pass `true` to disable the field across **all** Admin Panel surfaces — the edit form, list column selector, list filter dropdown, groupBy selector, and bulk edit modal:

```ts
fields: [
{
name: 'internalRef',
type: 'text',
admin: { disabled: true }, // hidden from every admin surface
},
]
```

Pass `false` (or omit the property) to enable the field everywhere.

#### Disable per area (object)

Pass an object to disable the field in specific areas only. Any key not set defaults to enabled:

| Key | Disables |
| ---------- | --------------------------- |
| `field` | Edit view form rendering. |
| `column` | List view column selector. |
| `filter` | List view filter dropdown. |
| `groupBy` | List view groupBy selector. |
| `bulkEdit` | Bulk edit modal. |

```ts
fields: [
{
name: 'internalRef',
type: 'text',
admin: { disabled: { column: true, filter: true } },
},
]
```

UI fields default to `disabled: { bulkEdit: true }` so they are excluded from bulk edit unless explicitly enabled.

### Field Descriptions

Expand Down
16 changes: 8 additions & 8 deletions docs/fields/ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export const MyUIField: Field = {

## Config Options

| Option | Description |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More details](./overview#field). |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More details](./overview#cell). |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| Option | Description |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More details](./overview#field). |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More details](./overview#cell). |
| **`admin.disabled`** | Pass `true` to disable the UI field everywhere, or an object `{ field?, column?, filter?, groupBy?, bulkEdit? }` for granular control. UI fields default to `disabled: { bulkEdit: true }`. [More details](./overview#disabled). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

_\* An asterisk denotes that a property is required._

Expand Down
1 change: 1 addition & 0 deletions packages/codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t

## Transforms

- `migrate-disabled-fields` — migrates `field.admin.disableListColumn`, `disableListFilter`, `disableGroupBy`, `disableBulkEdit` into the consolidated `field.admin.disabled` object form.
- `globals-components-edit` — Globals: rename `admin.components.elements` to `admin.components.edit` and hoist `Description` to top-level `admin.components.Description` to match Collection conventions.
- `migrate-force-select` — migrates `forceSelect: { ... }` on Collection/Global configs to a `select` function that augments the caller's `select` when present and returns `undefined` (preserving full-document reads) when not. Shallow values become a spread (`{ ...select, ... }`); nested values use `deepMergeSimple` from `payload/shared` (auto-imported) to preserve the previous deep-merge semantics. Non-literal values, sibling `select` already present, and unsupported member kinds are surfaced as notes for manual review.
- `migrate-hide-api-url` — migrates `admin.hideAPIURL: true` to `admin.components.views.edit.api.tab.condition: () => false` on collection and global configs.
Expand Down
8 changes: 6 additions & 2 deletions packages/codemod/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { IndentationText, Project } from 'ts-morph'
import { IndentationText, Project, QuoteKind } from 'ts-morph'

import type { TransformRunResult } from './runner.js'
import type { Transform } from './types.js'
Expand Down Expand Up @@ -75,7 +75,11 @@ function printList(): void {
}

function loadProject(path: string): Project {
const manipulationSettings = { indentationText: IndentationText.TwoSpaces }
const manipulationSettings = {
indentationText: IndentationText.TwoSpaces,
quoteKind: QuoteKind.Single,
useTrailingCommas: true,
}
const tsconfigPath = resolve(path, 'tsconfig.json')
if (existsSync(tsconfigPath)) {
return new Project({ manipulationSettings, tsConfigFilePath: tsconfigPath })
Expand Down
2 changes: 2 additions & 0 deletions packages/codemod/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import type { Transform } from './types.js'

import { exampleNoop } from './transforms/example-noop/index.js'
import { globalsComponentsEdit } from './transforms/globals-components-edit/index.js'
import { migrateDisabledFields } from './transforms/migrate-disabled-fields/index.js'
import { migrateForceSelect } from './transforms/migrate-force-select/index.js'
import { migrateHideAPIURL } from './transforms/migrate-hide-api-url/index.js'

export const transforms: Transform[] = [
exampleNoop,
migrateHideAPIURL,
globalsComponentsEdit,
migrateDisabledFields,
migrateForceSelect,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const Posts = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
admin: {
disableListColumn: true,
disableListFilter: true,
},
},
{
name: 'slug',
type: 'text',
admin: {
disableBulkEdit: true,
disableGroupBy: true,
},
},
{
name: 'normal',
type: 'text',
},
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const Posts = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
admin: {
disabled: { column: true, filter: true },
},
},
{
name: 'slug',
type: 'text',
admin: {
disabled: { bulkEdit: true, groupBy: true },
},
},
{
name: 'normal',
type: 'text',
},
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'

import { runTransform } from '../../utils/test-helpers.js'
import { migrateDisabledFields } from './index.js'

const here = dirname(fileURLToPath(import.meta.url))
const fixture = (name: string) => readFile(join(here, name), 'utf8')

describe('migrate-disabled-fields', () => {
it('migrates basic four-flag input', async () => {
const input = await fixture('basic.input.ts')
const output = await fixture('basic.output.ts')

const result = await runTransform({ source: input, transform: migrateDisabledFields })

expect(result).toBe(output)
})

it('is idempotent', async () => {
const output = await fixture('basic.output.ts')

const result = await runTransform({ source: output, transform: migrateDisabledFields })

expect(result).toBe(output)
})

it('merges new keys into existing disabled object literal', async () => {
const input = await fixture('merge.input.ts')
const output = await fixture('merge.output.ts')

const result = await runTransform({ source: input, transform: migrateDisabledFields })

expect(result).toBe(output)
})

it('preserves disabled: true and drops redundant flags', async () => {
const input = await fixture('preserve-true.input.ts')
const output = await fixture('preserve-true.output.ts')

const result = await runTransform({ source: input, transform: migrateDisabledFields })

expect(result).toBe(output)
})

it('no-ops on code without old props', async () => {
const input = await fixture('non-matching.input.ts')
const output = await fixture('non-matching.output.ts')

const result = await runTransform({ source: input, transform: migrateDisabledFields })

expect(result).toBe(output)
})
})
Loading
Loading