@payloadcms/codemod 4.0.0-beta.0 → 4.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +3 -2
  2. package/dist/registry.d.ts.map +1 -1
  3. package/dist/registry.js +2 -0
  4. package/dist/registry.js.map +1 -1
  5. package/dist/runbook/v4.mdx +92 -76
  6. package/dist/transforms/migrate-displayed-field-value-args/basic.input.js +4 -0
  7. package/dist/transforms/migrate-displayed-field-value-args/basic.input.js.map +1 -0
  8. package/dist/transforms/migrate-displayed-field-value-args/basic.output.js +8 -0
  9. package/dist/transforms/migrate-displayed-field-value-args/basic.output.js.map +1 -0
  10. package/dist/transforms/migrate-displayed-field-value-args/index.d.ts +3 -0
  11. package/dist/transforms/migrate-displayed-field-value-args/index.d.ts.map +1 -0
  12. package/dist/transforms/migrate-displayed-field-value-args/index.js +84 -0
  13. package/dist/transforms/migrate-displayed-field-value-args/index.js.map +1 -0
  14. package/dist/transforms/migrate-displayed-field-value-args/namespace.input.js +4 -0
  15. package/dist/transforms/migrate-displayed-field-value-args/namespace.input.js.map +1 -0
  16. package/dist/transforms/migrate-displayed-field-value-args/namespace.output.js +8 -0
  17. package/dist/transforms/migrate-displayed-field-value-args/namespace.output.js.map +1 -0
  18. package/dist/transforms/migrate-displayed-field-value-args/no-match.input.js +4 -0
  19. package/dist/transforms/migrate-displayed-field-value-args/no-match.input.js.map +1 -0
  20. package/dist/transforms/migrate-displayed-field-value-args/no-match.output.js +4 -0
  21. package/dist/transforms/migrate-displayed-field-value-args/no-match.output.js.map +1 -0
  22. package/dist/upgrade/defaultTag.d.ts +1 -1
  23. package/dist/upgrade/defaultTag.js +2 -2
  24. package/dist/upgrade/defaultTag.js.map +1 -1
  25. package/dist/upgrade/prompt.js +2 -2
  26. package/dist/upgrade/prompt.js.map +1 -1
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,7 @@ the prompt instead of prompting.
42
42
  `npx @payloadcms/codemod upgrade run` performs the deterministic v3 -> v4 slice against the current
43
43
  directory (run it from your project root):
44
44
 
45
- 1. Resolves the current Payload version from the npm registry, using the dist-tag this CLI was installed under (`beta` if run as `@payloadcms/codemod@beta`, else `canary`; override with `--tag <dist-tag>`).
45
+ 1. Resolves the current Payload version from the npm registry, using the dist-tag this CLI was installed under (its own prerelease id, else `beta`; override with `--tag <dist-tag>`).
46
46
  2. Rewrites `package.json`: pins `payload` + every `@payloadcms/*` to that exact version in
47
47
  lockstep, removes dependency overrides pinning them, converts their carets to exact, and writes
48
48
  the TypeScript / `@types/node` / `engines.node` floors. The `@payloadcms/eslint-*` packages are
@@ -57,7 +57,7 @@ prints the required Next target.
57
57
 
58
58
  Flags:
59
59
 
60
- - `--tag <dist-tag>`: dist-tag to resolve Payload versions from. Defaults to the dist-tag this CLI was installed under (`beta` when run as `@payloadcms/codemod@beta`, else `canary`).
60
+ - `--tag <dist-tag>`: dist-tag to resolve Payload versions from. Defaults to the dist-tag this CLI was installed under (its own prerelease id, else `beta`).
61
61
  - `--dry`: preview the `package.json` changes and planned steps; write and install nothing.
62
62
  - `--force`: skip the dirty-git-tree warning.
63
63
 
@@ -84,6 +84,7 @@ The tool loads your project via [ts-morph](https://ts-morph.com/), using your `t
84
84
 
85
85
  - `migrate-list-view-select-api`: Removes `admin.enableListViewSelectAPI` from Collection Configs. The List View's Select API is the default in v4.
86
86
  - `migrate-disabled-fields`: migrates `field.admin.disableListColumn`, `disableListFilter`, `disableGroupBy`, `disableBulkEdit` and their equivalents on `imageSize.admin` into the consolidated `disabled` object form.
87
+ - `migrate-displayed-field-value-args`: converts direct `getDisplayedFieldValue(cellData, field, i18n)` calls imported from `@payloadcms/ui/utilities/getDisplayedFieldValue` to the object argument form. Handles named, aliased, and namespace imports. Indirect references and unsupported argument shapes are reported for manual migration.
87
88
  - `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.
88
89
  - `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.
89
90
  - `migrate-field-component-types`: replaces field component aliases—including concrete fields, canonical generics, labels, descriptions, errors, diffs, and block row labels—with the corresponding props types wrapped in `React.FC`. Generic arguments are preserved. Explicit class components, re-exports, namespace references, and inline imports are left unchanged with notes for manual migration.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAsC3C,eAAO,MAAM,UAAU,EAAE,SAAS,EAoCjC,CAAA"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAuC3C,eAAO,MAAM,UAAU,EAAE,SAAS,EAqCjC,CAAA"}
package/dist/registry.js CHANGED
@@ -10,6 +10,7 @@ import { migrateBlockReferencesToBlocks } from './transforms/migrate-block-refer
10
10
  import { migrateBuildScript } from './transforms/migrate-build-script/index.js';
11
11
  import { migrateDbTypesSubpath } from './transforms/migrate-db-types-subpath/index.js';
12
12
  import { migrateDisabledFields } from './transforms/migrate-disabled-fields/index.js';
13
+ import { migrateDisplayedFieldValueArgs } from './transforms/migrate-displayed-field-value-args/index.js';
13
14
  import { migrateDocumentTitleContext } from './transforms/migrate-document-title-context/index.js';
14
15
  import { migrateFieldComponentTypes } from './transforms/migrate-field-component-types/index.js';
15
16
  import { migrateForceSelect } from './transforms/migrate-force-select/index.js';
@@ -41,6 +42,7 @@ export const transforms = [
41
42
  globalsComponentsEdit,
42
43
  migrateListViewSelectAPI,
43
44
  migrateDisabledFields,
45
+ migrateDisplayedFieldValueArgs,
44
46
  migrateForceSelect,
45
47
  migrateFieldComponentTypes,
46
48
  renameDiffValueProps,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts"],"sourcesContent":["import type { Transform } from './types.js'\n\nimport { addOverrideAccessTrue } from './transforms/add-override-access-true/index.js'\nimport { exampleNoop } from './transforms/example-noop/index.js'\nimport { globalsComponentsEdit } from './transforms/globals-components-edit/index.js'\nimport { migrateAdminContextProperties } from './transforms/migrate-admin-context-properties/index.js'\nimport { migrateAfterOperationRead } from './transforms/migrate-after-operation-read/index.js'\nimport { migrateAliasedExports } from './transforms/migrate-aliased-exports/index.js'\nimport { migrateAuthorshipDefault } from './transforms/migrate-authorship-default/index.js'\nimport { migrateAzureChunkLargeFiles } from './transforms/migrate-azure-chunk-large-files/index.js'\nimport { migrateBlockReferencesToBlocks } from './transforms/migrate-block-references-to-blocks/index.js'\nimport { migrateBuildScript } from './transforms/migrate-build-script/index.js'\nimport { migrateDbTypesSubpath } from './transforms/migrate-db-types-subpath/index.js'\nimport { migrateDisabledFields } from './transforms/migrate-disabled-fields/index.js'\nimport { migrateDocumentTitleContext } from './transforms/migrate-document-title-context/index.js'\nimport { migrateFieldComponentTypes } from './transforms/migrate-field-component-types/index.js'\nimport { migrateForceSelect } from './transforms/migrate-force-select/index.js'\nimport { migrateHideAPIURL } from './transforms/migrate-hide-api-url/index.js'\nimport { migrateImportExportHooks } from './transforms/migrate-import-export-hooks/index.js'\nimport { migrateListViewSelectAPI } from './transforms/migrate-list-view-select-api/index.js'\nimport { migrateNextGenerateViewportExport } from './transforms/migrate-next-generate-viewport-export/index.js'\nimport { migrateNextSubpathExports } from './transforms/migrate-next-subpath-exports/index.js'\nimport { migratePayloadRequestCreation } from './transforms/migrate-payload-request-creation/index.js'\nimport { migrateSharpToTransformer } from './transforms/migrate-sharp-to-transformer/index.js'\nimport { migrateSlugField } from './transforms/migrate-slug-field/index.js'\nimport { migrateStorageAdaptersToConfig } from './transforms/migrate-storage-adapters-to-config/index.js'\nimport { migrateVersionsDefault } from './transforms/migrate-versions-default/index.js'\nimport { removeDefaultLocalePublishOption } from './transforms/remove-default-locale-publish-option/index.js'\nimport { removeGroupByTrue } from './transforms/remove-group-by-true/index.js'\nimport { removeLocalizeStatusConfig } from './transforms/remove-localize-status-config/index.js'\nimport { removePublishSpecificLocale } from './transforms/remove-publish-specific-locale/index.js'\nimport { removeStorageAlwaysInsertFields } from './transforms/remove-storage-always-insert-fields/index.js'\nimport { removeVersionsTrue } from './transforms/remove-versions-true/index.js'\nimport { renameDiffValueProps } from './transforms/rename-diff-value-props/index.js'\nimport { renameExperimentalTableFeature } from './transforms/rename-experimental-table-feature/index.js'\nimport { renameStorageAdaptersToStorage } from './transforms/rename-storage-adapters-to-storage/index.js'\nimport { renameTypescriptSchemaToJsonSchema } from './transforms/rename-typescript-schema-to-json-schema/index.js'\n\nexport const transforms: Transform[] = [\n exampleNoop,\n addOverrideAccessTrue,\n migrateAfterOperationRead,\n migrateHideAPIURL,\n globalsComponentsEdit,\n migrateListViewSelectAPI,\n migrateDisabledFields,\n migrateForceSelect,\n migrateFieldComponentTypes,\n renameDiffValueProps,\n migrateAliasedExports,\n migratePayloadRequestCreation,\n migrateAdminContextProperties,\n migrateBlockReferencesToBlocks,\n migrateBuildScript,\n migrateDocumentTitleContext,\n migrateSharpToTransformer,\n migrateStorageAdaptersToConfig,\n migrateAzureChunkLargeFiles,\n renameStorageAdaptersToStorage,\n migrateImportExportHooks,\n migrateDbTypesSubpath,\n migrateNextGenerateViewportExport,\n migrateNextSubpathExports,\n migrateSlugField,\n migrateVersionsDefault,\n migrateAuthorshipDefault,\n removeDefaultLocalePublishOption,\n removeGroupByTrue,\n removeLocalizeStatusConfig,\n removeVersionsTrue,\n removePublishSpecificLocale,\n removeStorageAlwaysInsertFields,\n renameTypescriptSchemaToJsonSchema,\n renameExperimentalTableFeature,\n]\n"],"names":["addOverrideAccessTrue","exampleNoop","globalsComponentsEdit","migrateAdminContextProperties","migrateAfterOperationRead","migrateAliasedExports","migrateAuthorshipDefault","migrateAzureChunkLargeFiles","migrateBlockReferencesToBlocks","migrateBuildScript","migrateDbTypesSubpath","migrateDisabledFields","migrateDocumentTitleContext","migrateFieldComponentTypes","migrateForceSelect","migrateHideAPIURL","migrateImportExportHooks","migrateListViewSelectAPI","migrateNextGenerateViewportExport","migrateNextSubpathExports","migratePayloadRequestCreation","migrateSharpToTransformer","migrateSlugField","migrateStorageAdaptersToConfig","migrateVersionsDefault","removeDefaultLocalePublishOption","removeGroupByTrue","removeLocalizeStatusConfig","removePublishSpecificLocale","removeStorageAlwaysInsertFields","removeVersionsTrue","renameDiffValueProps","renameExperimentalTableFeature","renameStorageAdaptersToStorage","renameTypescriptSchemaToJsonSchema","transforms"],"mappings":"AAEA,SAASA,qBAAqB,QAAQ,iDAAgD;AACtF,SAASC,WAAW,QAAQ,qCAAoC;AAChE,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,6BAA6B,QAAQ,yDAAwD;AACtG,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,wBAAwB,QAAQ,mDAAkD;AAC3F,SAASC,2BAA2B,QAAQ,wDAAuD;AACnG,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,qBAAqB,QAAQ,iDAAgD;AACtF,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,2BAA2B,QAAQ,uDAAsD;AAClG,SAASC,0BAA0B,QAAQ,sDAAqD;AAChG,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,wBAAwB,QAAQ,oDAAmD;AAC5F,SAASC,wBAAwB,QAAQ,qDAAoD;AAC7F,SAASC,iCAAiC,QAAQ,8DAA6D;AAC/G,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,6BAA6B,QAAQ,yDAAwD;AACtG,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,gBAAgB,QAAQ,2CAA0C;AAC3E,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,sBAAsB,QAAQ,iDAAgD;AACvF,SAASC,gCAAgC,QAAQ,6DAA4D;AAC7G,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,0BAA0B,QAAQ,sDAAqD;AAChG,SAASC,2BAA2B,QAAQ,uDAAsD;AAClG,SAASC,+BAA+B,QAAQ,4DAA2D;AAC3G,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,oBAAoB,QAAQ,gDAA+C;AACpF,SAASC,8BAA8B,QAAQ,0DAAyD;AACxG,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,kCAAkC,QAAQ,gEAA+D;AAElH,OAAO,MAAMC,aAA0B;IACrClC;IACAD;IACAI;IACAW;IACAb;IACAe;IACAN;IACAG;IACAD;IACAkB;IACA1B;IACAe;IACAjB;IACAK;IACAC;IACAG;IACAS;IACAE;IACAhB;IACA0B;IACAjB;IACAN;IACAQ;IACAC;IACAG;IACAE;IACAlB;IACAmB;IACAC;IACAC;IACAG;IACAF;IACAC;IACAK;IACAF;CACD,CAAA"}
1
+ {"version":3,"sources":["../src/registry.ts"],"sourcesContent":["import type { Transform } from './types.js'\n\nimport { addOverrideAccessTrue } from './transforms/add-override-access-true/index.js'\nimport { exampleNoop } from './transforms/example-noop/index.js'\nimport { globalsComponentsEdit } from './transforms/globals-components-edit/index.js'\nimport { migrateAdminContextProperties } from './transforms/migrate-admin-context-properties/index.js'\nimport { migrateAfterOperationRead } from './transforms/migrate-after-operation-read/index.js'\nimport { migrateAliasedExports } from './transforms/migrate-aliased-exports/index.js'\nimport { migrateAuthorshipDefault } from './transforms/migrate-authorship-default/index.js'\nimport { migrateAzureChunkLargeFiles } from './transforms/migrate-azure-chunk-large-files/index.js'\nimport { migrateBlockReferencesToBlocks } from './transforms/migrate-block-references-to-blocks/index.js'\nimport { migrateBuildScript } from './transforms/migrate-build-script/index.js'\nimport { migrateDbTypesSubpath } from './transforms/migrate-db-types-subpath/index.js'\nimport { migrateDisabledFields } from './transforms/migrate-disabled-fields/index.js'\nimport { migrateDisplayedFieldValueArgs } from './transforms/migrate-displayed-field-value-args/index.js'\nimport { migrateDocumentTitleContext } from './transforms/migrate-document-title-context/index.js'\nimport { migrateFieldComponentTypes } from './transforms/migrate-field-component-types/index.js'\nimport { migrateForceSelect } from './transforms/migrate-force-select/index.js'\nimport { migrateHideAPIURL } from './transforms/migrate-hide-api-url/index.js'\nimport { migrateImportExportHooks } from './transforms/migrate-import-export-hooks/index.js'\nimport { migrateListViewSelectAPI } from './transforms/migrate-list-view-select-api/index.js'\nimport { migrateNextGenerateViewportExport } from './transforms/migrate-next-generate-viewport-export/index.js'\nimport { migrateNextSubpathExports } from './transforms/migrate-next-subpath-exports/index.js'\nimport { migratePayloadRequestCreation } from './transforms/migrate-payload-request-creation/index.js'\nimport { migrateSharpToTransformer } from './transforms/migrate-sharp-to-transformer/index.js'\nimport { migrateSlugField } from './transforms/migrate-slug-field/index.js'\nimport { migrateStorageAdaptersToConfig } from './transforms/migrate-storage-adapters-to-config/index.js'\nimport { migrateVersionsDefault } from './transforms/migrate-versions-default/index.js'\nimport { removeDefaultLocalePublishOption } from './transforms/remove-default-locale-publish-option/index.js'\nimport { removeGroupByTrue } from './transforms/remove-group-by-true/index.js'\nimport { removeLocalizeStatusConfig } from './transforms/remove-localize-status-config/index.js'\nimport { removePublishSpecificLocale } from './transforms/remove-publish-specific-locale/index.js'\nimport { removeStorageAlwaysInsertFields } from './transforms/remove-storage-always-insert-fields/index.js'\nimport { removeVersionsTrue } from './transforms/remove-versions-true/index.js'\nimport { renameDiffValueProps } from './transforms/rename-diff-value-props/index.js'\nimport { renameExperimentalTableFeature } from './transforms/rename-experimental-table-feature/index.js'\nimport { renameStorageAdaptersToStorage } from './transforms/rename-storage-adapters-to-storage/index.js'\nimport { renameTypescriptSchemaToJsonSchema } from './transforms/rename-typescript-schema-to-json-schema/index.js'\n\nexport const transforms: Transform[] = [\n exampleNoop,\n addOverrideAccessTrue,\n migrateAfterOperationRead,\n migrateHideAPIURL,\n globalsComponentsEdit,\n migrateListViewSelectAPI,\n migrateDisabledFields,\n migrateDisplayedFieldValueArgs,\n migrateForceSelect,\n migrateFieldComponentTypes,\n renameDiffValueProps,\n migrateAliasedExports,\n migratePayloadRequestCreation,\n migrateAdminContextProperties,\n migrateBlockReferencesToBlocks,\n migrateBuildScript,\n migrateDocumentTitleContext,\n migrateSharpToTransformer,\n migrateStorageAdaptersToConfig,\n migrateAzureChunkLargeFiles,\n renameStorageAdaptersToStorage,\n migrateImportExportHooks,\n migrateDbTypesSubpath,\n migrateNextGenerateViewportExport,\n migrateNextSubpathExports,\n migrateSlugField,\n migrateVersionsDefault,\n migrateAuthorshipDefault,\n removeDefaultLocalePublishOption,\n removeGroupByTrue,\n removeLocalizeStatusConfig,\n removeVersionsTrue,\n removePublishSpecificLocale,\n removeStorageAlwaysInsertFields,\n renameTypescriptSchemaToJsonSchema,\n renameExperimentalTableFeature,\n]\n"],"names":["addOverrideAccessTrue","exampleNoop","globalsComponentsEdit","migrateAdminContextProperties","migrateAfterOperationRead","migrateAliasedExports","migrateAuthorshipDefault","migrateAzureChunkLargeFiles","migrateBlockReferencesToBlocks","migrateBuildScript","migrateDbTypesSubpath","migrateDisabledFields","migrateDisplayedFieldValueArgs","migrateDocumentTitleContext","migrateFieldComponentTypes","migrateForceSelect","migrateHideAPIURL","migrateImportExportHooks","migrateListViewSelectAPI","migrateNextGenerateViewportExport","migrateNextSubpathExports","migratePayloadRequestCreation","migrateSharpToTransformer","migrateSlugField","migrateStorageAdaptersToConfig","migrateVersionsDefault","removeDefaultLocalePublishOption","removeGroupByTrue","removeLocalizeStatusConfig","removePublishSpecificLocale","removeStorageAlwaysInsertFields","removeVersionsTrue","renameDiffValueProps","renameExperimentalTableFeature","renameStorageAdaptersToStorage","renameTypescriptSchemaToJsonSchema","transforms"],"mappings":"AAEA,SAASA,qBAAqB,QAAQ,iDAAgD;AACtF,SAASC,WAAW,QAAQ,qCAAoC;AAChE,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,6BAA6B,QAAQ,yDAAwD;AACtG,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,wBAAwB,QAAQ,mDAAkD;AAC3F,SAASC,2BAA2B,QAAQ,wDAAuD;AACnG,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,qBAAqB,QAAQ,iDAAgD;AACtF,SAASC,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,2BAA2B,QAAQ,uDAAsD;AAClG,SAASC,0BAA0B,QAAQ,sDAAqD;AAChG,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,wBAAwB,QAAQ,oDAAmD;AAC5F,SAASC,wBAAwB,QAAQ,qDAAoD;AAC7F,SAASC,iCAAiC,QAAQ,8DAA6D;AAC/G,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,6BAA6B,QAAQ,yDAAwD;AACtG,SAASC,yBAAyB,QAAQ,qDAAoD;AAC9F,SAASC,gBAAgB,QAAQ,2CAA0C;AAC3E,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,sBAAsB,QAAQ,iDAAgD;AACvF,SAASC,gCAAgC,QAAQ,6DAA4D;AAC7G,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,0BAA0B,QAAQ,sDAAqD;AAChG,SAASC,2BAA2B,QAAQ,uDAAsD;AAClG,SAASC,+BAA+B,QAAQ,4DAA2D;AAC3G,SAASC,kBAAkB,QAAQ,6CAA4C;AAC/E,SAASC,oBAAoB,QAAQ,gDAA+C;AACpF,SAASC,8BAA8B,QAAQ,0DAAyD;AACxG,SAASC,8BAA8B,QAAQ,2DAA0D;AACzG,SAASC,kCAAkC,QAAQ,gEAA+D;AAElH,OAAO,MAAMC,aAA0B;IACrCnC;IACAD;IACAI;IACAY;IACAd;IACAgB;IACAP;IACAC;IACAG;IACAD;IACAkB;IACA3B;IACAgB;IACAlB;IACAK;IACAC;IACAI;IACAS;IACAE;IACAjB;IACA2B;IACAjB;IACAP;IACAS;IACAC;IACAG;IACAE;IACAnB;IACAoB;IACAC;IACAC;IACAG;IACAF;IACAC;IACAK;IACAF;CACD,CAAA"}
@@ -1,13 +1,11 @@
1
1
  ---
2
2
  title: 3.0 to 4.0 Migration Guide
3
3
  label: 3.0 to 4.0
4
- order: 15
4
+ order: 20
5
5
  desc: Upgrade guide for Payload 3.x projects migrating to 4.0.
6
6
  keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react
7
7
  ---
8
8
 
9
- # Payload 3.0 to 4.0 Migration Guide
10
-
11
9
  All breaking changes are listed below. If you encounter changes that are not explicitly listed here, please consider contributing to this documentation by submitting a PR.
12
10
 
13
11
  ## Codemod
@@ -122,9 +120,9 @@ A token is also rejected if its `collection` is not auth-enabled or has `disable
122
120
 
123
121
  See [Token Data](../authentication/token-data) for the full token data shape.
124
122
 
125
- ### `overrideAccess` now defaults to `false` on Local API operations
123
+ ### Local API access is now enforced by default #overrideaccess-now-defaults-to-false-on-local-api-operations
126
124
 
127
- Local API operations whose optional `overrideAccess` value previously defaulted to `true` now default it to `false`, so omitting it respects Access Control instead. Operations that do not expose `overrideAccess`, and operations such as `resetPassword` where it was already required or did not default to `true`, are unaffected by this change.
125
+ `overrideAccess` now defaults to `false` on Local API operations, so omitting it respects Access Control instead. Operations that do not expose `overrideAccess`, and operations such as `resetPassword` where it was already required or did not default to `true`, are unaffected by this change.
128
126
 
129
127
  ```diff
130
128
  const posts = await payload.find({
@@ -181,7 +179,7 @@ const syncCustomer: CollectionBeforeChangeHook = async ({
181
179
 
182
180
  Sanitized collection and global access types now require a `validate` key. Payload supplies this key during configuration sanitization and uses `update` access by default. Code that constructs a sanitized access object directly must add the key.
183
181
 
184
- ### Collection and Global Access Control callbacks receive `slug`
182
+ ### Access Control callbacks now receive document slugs
185
183
 
186
184
  Collection and Global Access Control callback arguments now include the document's `slug`. This additive property is unlikely to affect most projects, but code that manually invokes access callbacks or `executeAccess` must now pass `slug`. Code that validates, enumerates, or compares the exact callback argument shape may also need updating.
187
185
 
@@ -271,7 +269,7 @@ Payload adds built-in command import references to `cli.commands` while sanitizi
271
269
 
272
270
  See the [Payload CLI documentation](../configuration/cli) for the complete API.
273
271
 
274
- ### `migrateCLI` removed
272
+ ### Legacy migration CLI export removed
275
273
 
276
274
  The undocumented `migrateCLI` export has been removed. It accepted an already-sanitized config, a minimist-shaped `parsedArgs` object, and a special `migrationDir` override. This duplicated migration command registration and kept the old argument format alive after the main CLI moved to Commander.
277
275
 
@@ -300,7 +298,7 @@ The spawned CLI discovers the Payload config from `cwd` or `PAYLOAD_CONFIG_PATH`
300
298
 
301
299
  `generateTypes` now returns `{ types, outputFile, written }` instead of returning the generated source directly when using `returnString: true`. `generateImportMap` now returns `{ outputFile, written }`, and `payload.db.generateSchema()` returns `{ outputFile }`. Code that ignores these return values does not need to change.
302
300
 
303
- ### Default query depth reduced to `1`
301
+ ### Default query depth has been reduced
304
302
 
305
303
  Queries that omit `depth` now populate one relationship level instead of two. This reduces database work and response size by default.
306
304
 
@@ -375,7 +373,7 @@ export const Posts: CollectionConfig = {
375
373
  }
376
374
  ```
377
375
 
378
- ### Authorship (`createdBy` / `updatedBy`) is now enabled by default
376
+ ### Authorship is now enabled by default
379
377
 
380
378
  Collections and globals now track authorship by default: Payload injects `createdBy` and `updatedBy` polymorphic relationship fields (related to every auth-enabled collection) and stamps them from `req.user` on each write. Previously there was no authorship tracking; you now opt out with `authorship: false`.
381
379
 
@@ -414,7 +412,7 @@ The `admin.enableListViewSelectAPI` Collection Config property has been removed.
414
412
 
415
413
  If you previously set `admin.enableListViewSelectAPI: true`, remove the property — the behavior is now default. The `migrate-list-view-select-api` transform in `@payloadcms/codemod` handles this automatically.
416
414
 
417
- ### Globals: `admin.components.elements` renamed to `admin.components.edit`
415
+ ### Global component slots now match Collections
418
416
 
419
417
  Global configs now use the same `admin.components` shape as Collections. The Edit View slot container is renamed from `elements` to `edit`, the `Description` slot is hoisted to top-level `admin.components.Description`, and `editMenuItems` is now supported.
420
418
 
@@ -442,7 +440,7 @@ Globals also now support custom views with arbitrary keys under `admin.component
442
440
 
443
441
  This change is auto-migrated by the `globals-components-edit` codemod.
444
442
 
445
- ### `forceSelect` removed in favor of a `select` function on Collections and Globals
443
+ ### Dynamic selection replaces static forced selection
446
444
 
447
445
  The static `forceSelect` config has been removed. Each Collection and Global now accepts a top-level `select` function that receives the current `operation`, `req`, and the caller's `select`, and returns the final `select` to use. Returning `undefined` leaves the caller's `select` unchanged.
448
446
 
@@ -467,9 +465,9 @@ The same change applies to Globals.
467
465
 
468
466
  Run `npx @payloadcms/codemod --transform migrate-force-select` to migrate automatically. The codemod handles object-literal `forceSelect` values, including nested ones (which previously deep-merged) — those are rewritten to call `deepMergeSimple` from `payload/shared`, with the import added automatically. Non-literal values, configs that already define a sibling `select`, and unsupported member kinds are surfaced as notes for manual review.
469
467
 
470
- #### The `admin.hideAPIURL` property on Collections and Globals has been removed
468
+ #### API tab visibility now uses a condition
471
469
 
472
- Use the existing `admin.components.views.edit.api.tab.condition` to hide the API tab instead.
470
+ `admin.hideAPIURL` has been removed from Collections and Globals. Use the existing `admin.components.views.edit.api.tab.condition` to hide the API tab instead.
473
471
 
474
472
  ```diff
475
473
  // collections/Posts.ts
@@ -497,7 +495,7 @@ export const PostsCollection: CollectionConfig = {
497
495
 
498
496
  Run `npx @payloadcms/codemod --transform remove-hide-api-url` to migrate automatically.
499
497
 
500
- ### Aliased type and utility re-exports from `@payloadcms/ui` and `@payloadcms/next`
498
+ ### Aliased UI and Next.js re-exports have been removed
501
499
 
502
500
  Several types and utilities that were re-exported from `@payloadcms/ui` and `@payloadcms/next/utilities` for backwards compatibility have been removed. The canonical exports live in `payload` and `payload/shared`; import directly from there.
503
501
 
@@ -616,7 +614,7 @@ npx @payloadcms/codemod --transform rename-diff-value-props
616
614
 
617
615
  The codemod updates functions whose props are typed with a Diff props or component type imported from `payload` (e.g. `TextFieldDiffClientProps`), or with a type in the same file built from one. It renames destructured props and reads such as `props.comparisonValue`. Untyped components and props passed on to other components (e.g. `<OtherDiff comparisonValue={...} />`) aren't changed automatically; the codemod prints a note for each leftover in files that import a Diff type.
618
616
 
619
- ### `DefaultValue` function variant is now typed to allow returning a `Promise` ([#17522](https://github.com/payloadcms/payload/pull/17522))
617
+ ### Default value functions can now return promises ([#17522](https://github.com/payloadcms/payload/pull/17522))
620
618
 
621
619
  The exported `DefaultValue` type's function variant previously declared a synchronous-only return type (`SerializableValue`), even though Payload has always awaited the result of a `defaultValue` function at runtime. The type now correctly reflects this and allows `Promise<SerializableValue> | SerializableValue`.
622
620
 
@@ -678,7 +676,7 @@ const interfaceName = global.typescript?.interface
678
676
 
679
677
  Properties filled during sanitization, including `_sanitized`, `admin`, `custom`, `label`, standard access functions, hook arrays, endpoints, and flattened fields, remain required. Use `GlobalConfig` for input and `SanitizedGlobalConfig` after sanitization.
680
678
 
681
- ### User types consolidated into `User` and `AuthenticatedUser`
679
+ ### User types have been consolidated
682
680
 
683
681
  Payload's user types have been consolidated so each one has a clear job. This completes a cleanup already planned in 3.x: `UntypedUser` was deprecated for removal in 4.0, and `TypedUser` was marked to be renamed to `User` in 4.0.
684
682
 
@@ -744,7 +742,7 @@ If the field name is dynamic (configurable at runtime), index through `Record<st
744
742
  const value = (user as Record<string, unknown>)[fieldNameFromConfig]
745
743
  ```
746
744
 
747
- ### `multiTenantPlugin` and `searchPlugin` no longer take a type argument
745
+ ### Plugins now infer generated types automatically
748
746
 
749
747
  The `<ConfigType>` type parameter on `multiTenantPlugin` and `searchPlugin` — and on `MultiTenantPluginConfig` / `SearchPluginConfig` — has been removed. It only existed to override the `user` / `locale` types, which Payload now infers from your generated types automatically: `userHasAccessToAllTenants` receives the generated `User`, and `search`'s `skipSync` callback receives `TypedLocale | undefined`.
750
748
 
@@ -755,9 +753,9 @@ Drop the type argument — your callbacks are typed correctly without it.
755
753
  + multiTenantPlugin({ ... })
756
754
  ```
757
755
 
758
- ### `definePlugin`: `slug` is required and options moved to a named `options` argument
756
+ ### Plugin definitions now require a slug and named options
759
757
 
760
- Two changes to the experimental `definePlugin` helper:
758
+ The experimental `definePlugin` helper now requires a `slug` and passes plugin options through a named `options` argument:
761
759
 
762
760
  - **`slug` is now required.** It registers the plugin in the `plugins` map for cross-plugin discovery. Add one to every `definePlugin` call.
763
761
  - **Options are a named `options` argument.** They were previously spread into the `plugin` callback's args alongside `config` and `plugins`; they're now under a single `options` property. `TOptions` is also no longer constrained to `Record<string, unknown>`, so `interface` and generic option types work.
@@ -771,9 +769,9 @@ export const myPlugin = definePlugin<MyPluginOptions>({
771
769
  })
772
770
  ```
773
771
 
774
- ### `next/navigation` and `next/link` replaced by framework-agnostic `RouterAdapter` hooks
772
+ ### Admin routing is now framework-agnostic
775
773
 
776
- `@payloadcms/ui` no longer depends on `next` directly. Custom admin components (field components, custom views, plugins) that previously imported router hooks or the `Link` component from `next/*` must now import the framework-agnostic equivalents from `@payloadcms/ui`. Behavior is identical when running on Next.js — the `NextRouterAdapter` shipped by `@payloadcms/next` wires `next/navigation` hooks and `next/link` into the same context — but ui code can now also run on non-Next adapters (e.g. TanStack Start) without modification.
774
+ `@payloadcms/ui` no longer depends on `next` directly. Custom admin components (field components, custom views, plugins) that previously imported router hooks or the `Link` component from `next/*` must now import the framework-agnostic equivalents from `@payloadcms/ui`. Behavior is identical when running on Next.js — the `NextRouterAdapter` shipped by `@payloadcms/next` wires `next/navigation` hooks and `next/link` into the same context — but UI code can now also run on non-Next adapters such as TanStack Start without modification.
777
775
 
778
776
  **Hooks** — import from `@payloadcms/ui` instead of `next/navigation`:
779
777
 
@@ -825,7 +823,7 @@ If you were relying on Next-specific extras like `prefetch()` on the router inst
825
823
  />
826
824
  ```
827
825
 
828
- ### `@payloadcms/next/client`, `@payloadcms/next/rsc`, and `@payloadcms/next/templates` removed
826
+ ### Next.js subpath exports have moved to the UI package
829
827
 
830
828
  The `./client`, `./rsc`, and `./templates` subpath exports on `@payloadcms/next` were thin re-exports of components that now live in `@payloadcms/ui`. All admin elements (DocumentHeader, FormHeader, HierarchyTypeField, Logo, Nav) and templates (DefaultTemplate, MinimalTemplate) have been relocated to `@payloadcms/ui` and no longer depend on `next/*` modules. Import them directly from the canonical source.
831
829
 
@@ -863,7 +861,7 @@ If you reference any of these components by string path in your Payload config (
863
861
 
864
862
  Run `npx @payloadcms/codemod --transform migrate-next-subpath-exports` to migrate automatically. It rewrites both `import` declarations and string component paths.
865
863
 
866
- ### `title` and `setDocumentTitle` removed from `useDocumentInfo`
864
+ ### Document title state has moved to its own hook
867
865
 
868
866
  For performance reasons, the document title state has been split out of `DocumentInfoContext` into its own `DocumentTitleContext`. Access it through the `useDocumentTitle` hook, which exposes the same `title` and `setDocumentTitle` API. Components that subscribed to `useDocumentInfo` solely for the title will no longer re-render when unrelated document state changes.
869
867
 
@@ -883,7 +881,7 @@ If you only used `useDocumentInfo` for `title` / `setDocumentTitle`, drop the im
883
881
 
884
882
  Run `npx @payloadcms/codemod --transform migrate-document-title-context` to migrate automatically. The codemod splits mixed destructures, removes the `useDocumentInfo` import when no other properties are still used, and preserves any rename aliases (e.g. `{ title: docTitle }`).
885
883
 
886
- ### `useLocale` returns `null` without an active locale
884
+ ### Locale hooks can now return null
887
885
 
888
886
  The return type of `useLocale` is now `Locale | null`. It returns `null` when localization is disabled or configured without any locales. In Payload 3, the hook was typed as `Locale` and returned an empty object when localization was unavailable.
889
887
 
@@ -919,7 +917,7 @@ The deprecated `readOnly` property has been removed from `FieldType`, the object
919
917
 
920
918
  Read `readOnly` from your field component's props instead:
921
919
 
922
- ````diff
920
+ ```diff
923
921
  'use client'
924
922
  import type { TextFieldClientComponent } from 'payload'
925
923
  import { useField } from '@payloadcms/ui'
@@ -928,6 +926,7 @@ import { useField } from '@payloadcms/ui'
928
926
  - const { readOnly, value } = useField({ path })
929
927
  +export const MyField: TextFieldClientComponent = ({ path, readOnly }) => {
930
928
  + const { value } = useField({ path })
929
+ ```
931
930
 
932
931
  ### Deprecated `TableColumnsProvider` props removed
933
932
 
@@ -954,9 +953,9 @@ The following deprecated props have been removed from `TableColumnsProvider` and
954
953
  >
955
954
  {children}
956
955
  </TableColumnsProvider>
957
- ````
956
+ ```
958
957
 
959
- ### RichText adapter `i18n` property removed
958
+ ### Rich text adapters no longer expose translations
960
959
 
961
960
  The `i18n` property on `RichTextAdapter` (and its return type from adapter provider functions) has been removed. It was deprecated in v3 in favour of merging translations directly into `config.i18n.translations` inside the adapter provider.
962
961
 
@@ -1013,7 +1012,7 @@ export const MyFeature = createServerFeature({
1013
1012
 
1014
1013
  If an affected adapter or feature performs asynchronous work, move that work to an asynchronous plugin before config sanitization or to a runtime hook. Projects that only use `buildConfig(...)` and `lexicalEditor(...)` normally are not affected; `buildConfig` remains asynchronous.
1015
1014
 
1016
- ### `transactionIDPromise` removed from `PayloadRequest`
1015
+ ### Transaction requests now use a single identifier property
1017
1016
 
1018
1017
  The `transactionIDPromise` property on `PayloadRequest` has been removed. It was deprecated and unused — `transactionID` (which already supports `Promise<number | string>`) is the correct property to use for both resolved values and pending transactions.
1019
1018
 
@@ -1063,7 +1062,7 @@ Remove `diffMethod` from your props. If you passed it on to a diffing utility, p
1063
1062
  }) => {
1064
1063
  ```
1065
1064
 
1066
- ### Collection `afterOperation` hook: `operation: 'read'` removed
1065
+ ### Collection after-operation hooks no longer report read operations
1067
1066
 
1068
1067
  The deprecated `'read'` value for the `operation` argument of collection `afterOperation` hooks has been removed. Payload has not dispatched `afterOperation` with `operation: 'read'` for some time — the `find` and `findByID` operations report their own names (`'find'` and `'findByID'`) instead.
1069
1068
 
@@ -1086,14 +1085,14 @@ Run `npx @payloadcms/codemod --transform migrate-after-operation-read` to migrat
1086
1085
 
1087
1086
  The `operation: 'read'` value on the `beforeOperation` hook is unchanged.
1088
1087
 
1089
- ### `defaults` config export removed from `payload` ([#17103](https://github.com/payloadcms/payload/pull/17103))
1088
+ ### Static configuration defaults have been removed ([#17103](https://github.com/payloadcms/payload/pull/17103))
1090
1089
 
1091
- The defaults object exported from payload has been removed. It was deprecated because it was a single shared object and mutating it (or any config that used it as a base) leaked changes into every other consumer of the defaults.
1090
+ The `defaults` object exported from `payload` has been removed. It was deprecated because it was a single shared object and mutating it (or any config that used it as a base) leaked changes into every other consumer of the defaults.
1092
1091
 
1093
1092
  If you depended on reading a specific default value at runtime, read it from the sanitized config returned by buildConfig (or payload.config) instead of from the static defaults object.
1094
1093
  Alternatively, you can call `addDefaultsToConfig` to get the unsanitized config but populated with the default properties.
1095
1094
 
1096
- ### Plugin Import/Export: `toCSV` and `fromCSV` removed
1095
+ ### Import and export field callbacks have moved to hooks
1097
1096
 
1098
1097
  The `toCSV` and `fromCSV` field options in `custom['plugin-import-export']` have been removed. Use `hooks.beforeExport` and `hooks.beforeImport` instead — they work for both CSV and JSON formats.
1099
1098
 
@@ -1134,7 +1133,7 @@ The `fromCSV` → `hooks.beforeImport` change is non-breaking for the `data` par
1134
1133
 
1135
1134
  Run `npx @payloadcms/codemod --transform migrate-import-export-hooks` to migrate automatically. The codemod moves the function values into the correct hook positions and merges with any existing `hooks` object. Review argument names in the migrated functions — `row` references must be renamed to `siblingData` and `doc` references renamed to `data`.
1136
1135
 
1137
- ### DB Adapter `/types` Subpath Exports Removed
1136
+ ### Database adapter types now use the main package exports
1138
1137
 
1139
1138
  The `/types` subpath export has been removed from all Drizzle-based database adapter packages. All types are available directly from the main package entry point.
1140
1139
 
@@ -1193,7 +1192,7 @@ Older versions of Next.js are no longer supported. Update Next.js to **16.4.0**
1193
1192
 
1194
1193
  Payload's generated types are no longer guaranteed to work on TypeScript versions below **6.0.3** (previously 5.7.3). Upgrade the `typescript` dependency in your project to 6.0.3 or newer.
1195
1194
 
1196
- ### `@payloadcms/richtext-slate` has been removed ([#16359](https://github.com/payloadcms/payload/pull/16359))
1195
+ ### The Slate rich text editor has been removed ([#16359](https://github.com/payloadcms/payload/pull/16359))
1197
1196
 
1198
1197
  Starting in Payload 4.0, `@payloadcms/richtext-lexical` is the only supported rich text editor. The `@payloadcms/richtext-slate` package has been deleted and will no longer be published.
1199
1198
 
@@ -1208,7 +1207,7 @@ The rich text adapter pattern itself is unchanged — `editor: lexicalEditor({})
1208
1207
 
1209
1208
  If you are still using Slate, **do not upgrade to 4.0 yet.** Stay on the latest 3.x release and complete the [Slate → Lexical migration](https://payloadcms.com/docs/rich-text/migration) first.
1210
1209
 
1211
- ### `HTMLConverterFeature` and `lexicalHTML` have been removed ([#16548](https://github.com/payloadcms/payload/pull/16548))
1210
+ ### Legacy Lexical HTML converters have been removed ([#16548](https://github.com/payloadcms/payload/pull/16548))
1212
1211
 
1213
1212
  The deprecated `HTMLConverterFeature`, `lexicalHTML`, and the per-node `converters.html` API have been removed from `@payloadcms/richtext-lexical`. Use `convertLexicalToHTML` and `lexicalHTMLField` from the non-deprecated converter instead.
1214
1213
 
@@ -1232,7 +1231,7 @@ The deprecated `HTMLConverterFeature`, `lexicalHTML`, and the per-node `converte
1232
1231
 
1233
1232
  Custom nodes that define `converters.html` should export their converters instead.
1234
1233
 
1235
- ### Lexical: `isHTMLElement` removed — use lexical's built-in ([#17104](https://github.com/payloadcms/payload/pull/17104))
1234
+ ### Lexical now uses its built-in HTML element check ([#17104](https://github.com/payloadcms/payload/pull/17104))
1236
1235
 
1237
1236
  The deprecated `isHTMLElement` utility has been removed from `@payloadcms/richtext-lexical`. It was a thin wrapper around `x instanceof HTMLElement` that duplicated a utility already exported by `lexical`. Import it directly from `lexical` instead.
1238
1237
 
@@ -1243,7 +1242,7 @@ The deprecated `isHTMLElement` utility has been removed from `@payloadcms/richte
1243
1242
 
1244
1243
  `lexical`'s built-in `isHTMLElement` is the canonical implementation and narrows against the node's owning-document `HTMLElement`, so it is also safe across iframes and other realms.
1245
1244
 
1246
- ### Lexical: vendored `@lexical/markdown` fork removed and lexical upgraded to `0.52.0`
1245
+ ### Lexical now uses the upstream Markdown package
1247
1246
 
1248
1247
  `@payloadcms/richtext-lexical` no longer ships a vendored copy of `@lexical/markdown`. The package is now a direct dependency, every `@lexical/*` package was upgraded from `0.41.0` to `0.52.0`, and `@payloadcms/richtext-lexical/lexical/markdown` is a pure passthrough of `@lexical/markdown`.
1249
1248
 
@@ -1280,9 +1279,9 @@ If your app imports Lexical types or adds custom features, check these changes f
1280
1279
  - `ReturnType<Node['exportJSON']>` can now infer partial JSON, making fields such as `version` and `url` optional. If your code expects the full format, use the node's explicit serialized type, such as `SerializedLinkNode`. A normal `node.exportJSON()` call still returns the full format.
1281
1280
  - `LexicalNode.getCommonAncestor` was removed. Use `$getCommonAncestor(a, b)?.commonAncestor ?? null`; pass the parents of non-element nodes if you need the old method's behavior.
1282
1281
 
1283
- ### Lexical: `EXPERIMENTAL_TableFeature` renamed to `TableFeature`
1282
+ ### Lexical table support is no longer experimental
1284
1283
 
1285
- The table feature is no longer experimental, so it drops its prefix.
1284
+ `EXPERIMENTAL_TableFeature` has been renamed to `TableFeature` now that the table feature is no longer experimental.
1286
1285
 
1287
1286
  ```diff
1288
1287
  - import { EXPERIMENTAL_TableFeature } from '@payloadcms/richtext-lexical'
@@ -1300,9 +1299,10 @@ Stored content is unaffected.
1300
1299
 
1301
1300
  The `LexicalEditorProps` type exported from `@payloadcms/richtext-lexical` has been renamed to `LexicalEditorArgs`. It describes the arguments of the `lexicalEditor` function, not component props. The shape is unchanged.
1302
1301
 
1303
- ````diff
1302
+ ```diff
1304
1303
  - import type { LexicalEditorProps } from '@payloadcms/richtext-lexical'
1305
1304
  + import type { LexicalEditorArgs } from '@payloadcms/richtext-lexical'
1305
+ ```
1306
1306
 
1307
1307
  ### Lexical: `UploadDataImproved` replaced by `UploadData` ([#18606](https://github.com/payloadcms/payload/pull/18606))
1308
1308
 
@@ -1310,7 +1310,7 @@ The internal `UploadDataImproved` type has been removed, and its definition is n
1310
1310
 
1311
1311
  ```ts
1312
1312
  import type { UploadData } from '@payloadcms/richtext-lexical'
1313
- ````
1313
+ ```
1314
1314
 
1315
1315
  ### Lexical: the `version` of nodes is ignored
1316
1316
 
@@ -1333,7 +1333,7 @@ await upgradeLexicalData({ payload })
1333
1333
 
1334
1334
  **Payload doesn't add or remove `version` when it saves rich text.** Rich text created by Lexical, like in the admin panel, still has `version: 1` on every node, because Lexical's JSON export writes it. Payload's own nodes used to write their own numbers instead (like `2` for blocks), and now write `1` as well. Rich text sent through the API, CLI or MCP is saved as sent, so it may not contain `version`. The generated types still include `version`, marked as deprecated, so don't rely on it being there.
1335
1335
 
1336
- ### Field `typescriptSchema` renamed to `jsonSchema`
1336
+ ### Field schema configuration now reflects JSON Schema
1337
1337
 
1338
1338
  `typescriptSchema` accepted JSON Schema, not TypeScript, so the name lied. Renamed to `jsonSchema`. Same shape, same array of transforms. The underlying schema is now also consumed by the MCP plugin for runtime validation, not just type generation.
1339
1339
 
@@ -1346,9 +1346,9 @@ await upgradeLexicalData({ payload })
1346
1346
  }
1347
1347
  ```
1348
1348
 
1349
- ### RichText adapter `outputSchema` renamed to `jsonSchema`
1349
+ ### Rich text output schemas have been renamed
1350
1350
 
1351
- Same story on the editor side. The arguments object also gains `typeStringDefinitions: Set<string>` — a sink for raw TS source appended to `payload-types.ts`.
1351
+ The Rich Text adapter's `outputSchema` property has been renamed to `jsonSchema`. The arguments object also gains `typeStringDefinitions: Set<string>` — a sink for raw TS source appended to `payload-types.ts`.
1352
1352
 
1353
1353
  ```diff
1354
1354
  myAdapter: RichTextAdapter = {
@@ -1361,7 +1361,7 @@ Same story on the editor side. The arguments object also gains `typeStringDefini
1361
1361
 
1362
1362
  `@payloadcms/richtext-lexical` is updated. Only third-party adapters need attention.
1363
1363
 
1364
- ### `configToJSONSchema` returns `{ jsonSchema, typeStringDefinitions }`
1364
+ ### Schema generation now returns definitions alongside JSON Schema
1365
1365
 
1366
1366
  `configToJSONSchema` (from `payload`) used to return a `JSONSchema4` directly. It now returns an object with the schema plus the TS-source sink. Destructure to pull the schema out.
1367
1367
 
@@ -1372,7 +1372,7 @@ Same story on the editor side. The arguments object also gains `typeStringDefini
1372
1372
 
1373
1373
  If you run your own type generation, append `[...typeStringDefinitions].join('\n\n')` to the compiled output. Otherwise ignore it.
1374
1374
 
1375
- ### `fieldsToJSONSchema` switched to object arguments
1375
+ ### Field schema generation now uses object arguments
1376
1376
 
1377
1377
  `fieldsToJSONSchema` used to take 6 positional arguments. It now takes one object. The new shape also requires `typeStringDefinitions`, and `forceInlineBlocks` is a top-level sibling instead of nested under `opts`.
1378
1378
 
@@ -1396,9 +1396,9 @@ If you run your own type generation, append `[...typeStringDefinitions].join('\n
1396
1396
  + })
1397
1397
  ```
1398
1398
 
1399
- ### `entityToJSONSchema` gained a required `typeStringDefinitions` argument
1399
+ ### Entity schema generation now requires type definitions
1400
1400
 
1401
- Inserted at position 5 (between `defaultIDType` and `collectionIDFieldTypes`). The old `opts: ConfigToJSONSchemaOptions` is replaced by an optional positional `forceInlineBlocks?: boolean` at the end.
1401
+ `entityToJSONSchema` now requires a `typeStringDefinitions` argument at position 5, between `defaultIDType` and `collectionIDFieldTypes`. The old `opts: ConfigToJSONSchemaOptions` is replaced by an optional positional `forceInlineBlocks?: boolean` at the end.
1402
1402
 
1403
1403
  ```diff
1404
1404
  entityToJSONSchema(
@@ -1414,7 +1414,7 @@ Inserted at position 5 (between `defaultIDType` and `collectionIDFieldTypes`). T
1414
1414
  )
1415
1415
  ```
1416
1416
 
1417
- ### Lexical: `modifyJSONSchema` is gone
1417
+ ### Lexical nodes now provide their own JSON Schemas
1418
1418
 
1419
1419
  `generatedTypes.modifyJSONSchema` on lexical features (and the sanitized `modifyJSONSchemas` array) is removed. Features now contribute per-node JSON Schemas via `createNode({ jsonSchema, node })` instead of mutating a whole-field schema after the fact. The editor composes per-node schemas into the field's union automatically.
1420
1420
 
@@ -1470,7 +1470,7 @@ The same rule applies to lexical block nodes — they now emit as `SerializedBlo
1470
1470
 
1471
1471
  When two **different** blocks resolve to the same auto-derived name (same slug, different fields — e.g. a shared block overridden in one collection), the first keeps the clean name and any later block with a different shape gets a stable content-hash suffix (`Hero_3F2A1B0C`), so neither is silently mistyped. To choose the name yourself — or to resolve a collision with another type in your generated file (a collection, an array's `interfaceName`, etc.) — set an explicit `interfaceName` on the block. See [Block interface name collisions](/docs/typescript/generating-types#block-interface-name-collisions).
1472
1472
 
1473
- ### Blocks field `blockReferences` moved into `blocks`
1473
+ ### Block references now share the blocks array
1474
1474
 
1475
1475
  The `blockReferences` property on blocks fields has been removed. The `blocks` property now accepts both inline block configs and string slugs for blocks defined in the top-level Payload Config `blocks` array.
1476
1476
 
@@ -1501,7 +1501,7 @@ Inline blocks continue to work as before:
1501
1501
  }
1502
1502
  ```
1503
1503
 
1504
- ### Lexical: generated `payload-types.ts` shape changed
1504
+ ### Generated Lexical types are now strict
1505
1505
 
1506
1506
  `richText` fields backed by `lexicalEditor` used to emit a permissive `{ [k: string]: unknown }` per field. They now emit a discriminated union of strict per-node interfaces (`SerializedTextNode`, `SerializedParagraphNode`, `SerializedHeadingNode`, `SerializedBlockNode`, etc.) under a hash-named alias like `LexicalNodes_AB12CD34`.
1507
1507
 
@@ -1532,7 +1532,7 @@ Element nodes are now generic over their `children`, and you pass the node union
1532
1532
 
1533
1533
  `SerializedTextNode` is a leaf, so it stays bare. For the built-in nodes plus a few of your own, `WithDefaultNodes` threads the union for you - pass your custom node(s) as the generic (`type MyNodes = WithDefaultNodes<SerializedBlockNode<MyBlockData>>`), and they're usable anywhere a node can appear, not just at the top level. `DefaultTypedEditorState` with only built-in nodes needs no change.
1534
1534
 
1535
- ### Generated JSON Schema uses `$defs` instead of `definitions`
1535
+ ### Generated JSON Schema now uses modern definitions
1536
1536
 
1537
1537
  Payload builds a JSON Schema from your config to generate `payload-types.ts`. That schema now stores its reusable types under the modern `$defs` key instead of the older `definitions` key, and every internal reference points at `#/$defs/...` instead of `#/definitions/...`. This follows the current JSON Schema spec.
1538
1538
 
@@ -1590,7 +1590,7 @@ jobs: {
1590
1590
 
1591
1591
  Job documents can contain inputs, outputs, logs, and execution state, so avoid enabling write access unless your application specifically requires it.
1592
1592
 
1593
- ### Jobs: `jobs.depth` and `jobs.runHooks` have been removed ([#16414](https://github.com/payloadcms/payload/pull/16414))
1593
+ ### Job queries and hooks now have fixed behavior ([#16414](https://github.com/payloadcms/payload/pull/16414))
1594
1594
 
1595
1595
  The deprecated `jobs.depth` and `jobs.runHooks` config options have been removed. Job operations now always use direct database calls with depth `0`.
1596
1596
 
@@ -1598,7 +1598,7 @@ If you relied on either option, remove it from your `jobs` config. If you depend
1598
1598
 
1599
1599
  The `jobsCollectionOverrides` warning that detected extra hooks and prompted users to set `runHooks` has also been removed.
1600
1600
 
1601
- ### Jobs: `BaseJob` has been removed
1601
+ ### The legacy base job type has been removed
1602
1602
 
1603
1603
  The deprecated `BaseJob` type is no longer exported. Use `Job` everywhere you previously used `BaseJob`.
1604
1604
 
@@ -1656,9 +1656,9 @@ The deprecated `RunningJob` and `RunningJobSimple` types have also been removed.
1656
1656
 
1657
1657
  JSON workflow steps without a `task` must now provide an `inlineTask` handler. This removes a type escape that previously allowed an incomplete step even though it could not run.
1658
1658
 
1659
- ### Jobs: custom database adapters must implement `updateJobs`
1659
+ ### Custom database adapters must support job claiming
1660
1660
 
1661
- Payload now uses a unique `processingToken` when claiming jobs so multiple workers cannot run the same job. This adds an internal, nullable `processingToken` field to the `payload-jobs` collection. Existing SQL databases need a migration that adds this column. MongoDB does not require a migration.
1661
+ Custom database adapters must now implement `updateJobs` so Payload can use a unique `processingToken` when claiming jobs and prevent multiple workers from running the same job. This adds an internal, nullable `processingToken` field to the `payload-jobs` collection. Existing SQL databases need a migration that adds this column. MongoDB does not require a migration.
1662
1662
 
1663
1663
  The default `updateJobs` implementation has been removed because it could not safely claim jobs across multiple workers. If you maintain a custom database adapter, you must now provide `updateJobs`. It must only claim jobs that still match the provided `where` condition and, when a `processingToken` is provided, return only the jobs claimed with that token. The official Payload database adapters already implement this behavior.
1664
1664
 
@@ -1670,7 +1670,7 @@ Worker updates use a 30-second safety buffer by default, so an update only start
1670
1670
 
1671
1671
  Existing SQL databases need a migration that replaces `processing` with the nullable, indexed `processingUntil` column. MongoDB does not require a migration.
1672
1672
 
1673
- ### Jobs: the `state` field on task return values has been removed ([#16416](https://github.com/payloadcms/payload/pull/16416))
1673
+ ### Job task results no longer include state ([#16416](https://github.com/payloadcms/payload/pull/16416))
1674
1674
 
1675
1675
  Job task handlers and `inlineTask` callbacks can no longer use the `state` field to signal success or failure. Both shapes were deprecated in v3 and are now removed from tasks and inline tasks.
1676
1676
 
@@ -1695,7 +1695,7 @@ const myTask: TaskHandler<'MyTask'> = async ({ input }) => {
1695
1695
  }
1696
1696
  ```
1697
1697
 
1698
- ### Croner `sloppyRanges` compatibility setting removed ([#16546](https://github.com/payloadcms/payload/pull/16546))
1698
+ ### Cron expressions now require explicit ranges ([#16546](https://github.com/payloadcms/payload/pull/16546))
1699
1699
 
1700
1700
  The `sloppyRanges: true` setting that kept Croner v9 stepping syntax working after the v10 upgrade has been removed. Croner now enforces stricter cron parsing rules, so stepping expressions must use either `*` or an explicit range before the `/`.
1701
1701
 
@@ -1727,7 +1727,7 @@ Migrate any affected cron expressions:
1727
1727
  + cron: '30 * * * *'
1728
1728
  ```
1729
1729
 
1730
- ### `min` and `max` removed from `relationship` and `upload` fields ([#16547](https://github.com/payloadcms/payload/pull/16547))
1730
+ ### Relationship and upload limits now use row-based options ([#16547](https://github.com/payloadcms/payload/pull/16547))
1731
1731
 
1732
1732
  The deprecated `min` and `max` properties have been removed from `relationship` and `upload` fields with `hasMany: true`. Use `minRows` and `maxRows` instead.
1733
1733
 
@@ -1746,7 +1746,7 @@ The deprecated `min` and `max` properties have been removed from `relationship`
1746
1746
 
1747
1747
  The same change applies to `upload` fields with `hasMany: true`.
1748
1748
 
1749
- ### `allowLocalizedWithinLocalized` compatibility flag removed ([#16693](https://github.com/payloadcms/payload/pull/16693))
1749
+ ### Nested localized fields no longer need a compatibility flag ([#16693](https://github.com/payloadcms/payload/pull/16693))
1750
1750
 
1751
1751
  `config.compatibility.allowLocalizedWithinLocalized` and the `PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY` environment variable have been removed. Sanitize no longer strips `localized: true` from fields nested under a localized parent — `fieldShouldBeLocalized` decides this at runtime instead.
1752
1752
 
@@ -1760,7 +1760,7 @@ The same change applies to `upload` fields with `hasMany: true`.
1760
1760
  - Remove the `compatibility` block from your config. If your MongoDB data still has the redundant nested-localized shape, flatten the config and run a data migration.
1761
1761
  - Replace any direct `field.localized` reads in custom code with `fieldShouldBeLocalized({ field, parentIsLocalized })` from `payload/shared`.
1762
1762
 
1763
- ### Admin UI Publish button now defaults to the active locale, and `localization.defaultLocalePublishOption` has been removed
1763
+ ### Publishing now defaults to the active locale
1764
1764
 
1765
1765
  The Admin UI Publish button now always defaults to publishing only the active locale when a collection or global has localized fields and you have publish permission. "Publish all locales" is available as a secondary option in the Publish button's dropdown. This previously required opting in via `localization.defaultLocalePublishOption: 'active'`; the config option has been removed since active-locale-only is now the permanent default.
1766
1766
 
@@ -1782,7 +1782,7 @@ export default buildConfig({
1782
1782
 
1783
1783
  Run `npx @payloadcms/codemod --transform remove-default-locale-publish-option` to remove the property automatically. The codemod surfaces a note for any config that had it set to `'all'`, since that combination previously changed the default and now has no config equivalent.
1784
1784
 
1785
- ### Storage adapters must be declared under `storage`, not `plugins` ([#16596](https://github.com/payloadcms/payload/pull/16596))
1785
+ ### Storage adapters have moved to top-level configuration ([#16596](https://github.com/payloadcms/payload/pull/16596))
1786
1786
 
1787
1787
  Storage adapter packages (`@payloadcms/storage-s3`, `-azure`, `-gcs`, `-r2`, `-vercel-blob`) must now be placed under the top-level `storage` key in your Payload config instead of inside `plugins`.
1788
1788
 
@@ -1954,7 +1954,7 @@ uploadInstructions: {
1954
1954
 
1955
1955
  Both are optional. Custom handler props are now passed as `props`, and the supporting route is available as `endpointPath` instead of `serverHandlerPath`. The old Admin component `enabled` prop was removed.
1956
1956
 
1957
- ### `@payloadcms/storage-uploadthing` has been removed
1957
+ ### The Uploadthing storage adapter has been removed
1958
1958
 
1959
1959
  The Uploadthing storage adapter, deprecated in a previous release, has been deleted and will no longer be published. Migrate to another storage adapter such as `@payloadcms/storage-s3` before upgrading to 4.0.
1960
1960
 
@@ -2009,7 +2009,7 @@ azureStorage({
2009
2009
 
2010
2010
  Run `npx @payloadcms/codemod --transform migrate-azure-chunk-large-files` to remove the flag automatically (it collapses `clientUploads: { chunkLargeFiles: true }` to `clientUploads: true` and keeps any other `clientUploads` options).
2011
2011
 
2012
- ### Sharp moved out of core into `@payloadcms/transformer-sharp`
2012
+ ### Sharp is now an optional file transformer #sharp-moved-out-of-core-into-payloadcmstransformer-sharp
2013
2013
 
2014
2014
  Image resizing is no longer built into `payload` itself. It now ships as an official [file transformer](../upload/transformers), `@payloadcms/transformer-sharp`, that you register under `upload.transformers`.
2015
2015
 
@@ -2069,7 +2069,7 @@ If you inject a custom Sharp build or version, pass it the same way:
2069
2069
 
2070
2070
  `sharpTransformer` can also resize images at request time (`/api/media/file/photo.png?width=400`). This is new in Payload 4 and **disabled by default**, so a migrated config keeps serving only the original file and your pre-generated `variants`. The codemod doesn't enable it. To opt in, pass `dynamic: true` or `dynamic: { collections: ['media'] }` — see [Dynamic (request-time) resizing](../upload/transformers#dynamic-request-time-resizing), and [Securing dynamic resizing](../upload/transformers#securing-dynamic-resizing) before enabling it on a publicly readable collection.
2071
2071
 
2072
- #### The stored `sizes` field on upload documents is renamed to `variants`
2072
+ #### Upload image data now uses variants
2073
2073
 
2074
2074
  The group field that holds each generated image size on an upload document is now `variants` instead of `sizes`. Every place that reads or queries it changes:
2075
2075
 
@@ -2129,7 +2129,7 @@ Registering any transformer under `upload.transformers` — including `sharpTran
2129
2129
 
2130
2130
  To link to a file from your own code, including with transformer query parameters, use the new `generatePayloadFileURL` helper (exported from `payload` and `payload/shared`). It always builds the access-controlled Payload file endpoint, so links never bypass `access.read` by pointing straight at storage.
2131
2131
 
2132
- ### Storage adapters always insert the `prefix` field now — `alwaysInsertFields` removed
2132
+ ### Storage prefixes are now always included
2133
2133
 
2134
2134
  The `alwaysInsertFields` option (on `cloudStoragePlugin` and every `storage-*` adapter: `-s3`, `-azure`, `-gcs`, `-r2`, `-vercel-blob`) has been removed. The behavior it used to opt into — always inserting the hidden `prefix` field into the collection schema — is now unconditional, so the schema stays consistent whether or not a collection configures a `prefix` and whether or not the plugin is `enabled`.
2135
2135
 
@@ -2150,7 +2150,7 @@ Upload collections now store `original` metadata and a `prefix` and `_objectKey`
2150
2150
 
2151
2151
  **Postgres and SQLite:** after updating, run `payload migrate:create`, check that it adds these columns to each upload collection table and its versions table, then run `payload migrate`. Existing rows keep `null` values until their next write.
2152
2152
 
2153
- ### `FileSizeImproved` removed — use `FileSize` ([#16593](https://github.com/payloadcms/payload/pull/16593))
2153
+ ### File size types have been consolidated ([#16593](https://github.com/payloadcms/payload/pull/16593))
2154
2154
 
2155
2155
  `FileSizeImproved` has been merged into `FileSize`. The `url`, `width`, `height`, `filesize`, `mimeType`, and `filename` properties now accept `null` directly on `FileSize`, matching what the database stores for sizes that were not generated.
2156
2156
 
@@ -2159,9 +2159,9 @@ Upload collections now store `original` metadata and a `prefix` and `_objectKey`
2159
2159
  + import type { FileSize } from 'payload'
2160
2160
  ```
2161
2161
 
2162
- ### `ImageSize.admin` — `disableListColumn`, `disableListFilter`, and `disableGroupBy` removed ([#16593](https://github.com/payloadcms/payload/pull/16593))
2162
+ ### Image size admin controls have been consolidated ([#16593](https://github.com/payloadcms/payload/pull/16593))
2163
2163
 
2164
- The three individual boolean props have been replaced with a single `disabled` object, consistent with the shape used by all other fields.
2164
+ The `disableListColumn`, `disableListFilter`, and `disableGroupBy` properties under `ImageSize.admin` have been replaced with a single `disabled` object, consistent with the shape used by all other fields.
2165
2165
 
2166
2166
  ```diff
2167
2167
  {
@@ -2178,7 +2178,7 @@ The three individual boolean props have been replaced with a single `disabled` o
2178
2178
 
2179
2179
  Run `npx @payloadcms/codemod --transform migrate-disabled-fields` to migrate automatically.
2180
2180
 
2181
- ### `db-mongodb`: `connectOptions.useFacet` removed ([#16612](https://github.com/payloadcms/payload/pull/16612))
2181
+ ### MongoDB queries no longer use facet aggregation ([#16612](https://github.com/payloadcms/payload/pull/16612))
2182
2182
 
2183
2183
  The `useFacet` option in `mongooseAdapter` config has been removed. Payload no longer uses `$facet` aggregation, so the option has no effect.
2184
2184
 
@@ -2190,7 +2190,7 @@ The `useFacet` option in `mongooseAdapter` config has been removed. Payload no l
2190
2190
  })
2191
2191
  ```
2192
2192
 
2193
- ### `plugin-search`: `apiBasePath` config option removed ([#16597](https://github.com/payloadcms/payload/pull/16597))
2193
+ ### Search plugin API paths are now derived automatically ([#16597](https://github.com/payloadcms/payload/pull/16597))
2194
2194
 
2195
2195
  The deprecated `apiBasePath` option on the search plugin has been removed. The plugin now derives the correct path from the root config automatically.
2196
2196
 
@@ -2202,7 +2202,7 @@ The sha1 HMAC fallback for API key authentication has been removed. API keys are
2202
2202
 
2203
2203
  **How to migrate:** Open the user document for each affected API key holder and save it — this regenerates the key under sha256. Alternatively, generate a new API key from the admin panel.
2204
2204
 
2205
- ### `baseAPIKeyFields` was replaced by `createAPIKeyFields`
2205
+ ### API key fields now use a factory
2206
2206
 
2207
2207
  The `baseAPIKeyFields` export from `payload` has been removed. Use `createAPIKeyFields()` instead. It returns the same default auth API key storage fields, and also accepts overrides for projects or plugins that need to customize the generated field configs.
2208
2208
 
@@ -2231,9 +2231,9 @@ createAPIKeyFields({
2231
2231
  })
2232
2232
  ```
2233
2233
 
2234
- ### `@payloadcms/plugin-mcp` refactored: config API, built-in tool inputs, and user API keys
2234
+ ### MCP plugin configuration and authentication have changed
2235
2235
 
2236
- The MCP plugin was refactored. The public config API, the built-in tool inputs, and API-key authentication changed. Update your plugin config and recreate MCP credentials as user API keys.
2236
+ `@payloadcms/plugin-mcp` has a new public config API, built-in tool inputs, and API-key authentication flow. Update your plugin config and recreate MCP credentials as user API keys.
2237
2237
 
2238
2238
  **Collections and globals are now opt-out.** Drop the `enabled` flag. Every collection and global is exposed by default through the generic built-in tools. For collections, this includes tools like `getCollectionSchema`, `findDocuments`, `countDocuments`, `createDocuments`, `updateDocument`, `deleteDocuments`, `duplicateDocument`, and `findDistinct`, plus version tools when versions are enabled. For globals, this includes `getGlobalSchema`, `findGlobal`, and `updateGlobal`, plus version tools when versions are enabled. Turn individual operations off via simple config keys like `tools: { create: false }`.
2239
2239
 
@@ -2406,7 +2406,7 @@ By default, MCP items require an authenticated user. Built-in collection and glo
2406
2406
 
2407
2407
  **HTTP requests have a size limit.** `POST /api/mcp` now answers request bodies over 4 MiB with `413`. Base64 file uploads count toward this limit. Raise it with `mcp.maxRequestBodySize`.
2408
2408
 
2409
- ### `@payloadcms/ui/scss` export removed and legacy `--theme-*` tokens retired
2409
+ ### Admin UI styles now use CSS and semantic tokens
2410
2410
 
2411
2411
  The Admin UI styles were migrated from SCSS to plain CSS. The `@payloadcms/ui/scss` package export and its SCSS source files have been removed, and the legacy `--theme-*` CSS variables were retired in favor of semantic `--color-*` tokens and the raw `--ramp-*` color palette.
2412
2412
 
@@ -2482,7 +2482,7 @@ If you previously used `rest: false`, remove the property; omitting `rest` disab
2482
2482
  - `hasAPIKey` (3.x virtual field) is removed. Use `apiKeyLast4` and `docPermissions.fields.apiKey`.
2483
2483
  - Custom `access.read` on the generated `apiKey` field is inert — reads are hardcoded to deny.
2484
2484
 
2485
- ### Upload `abortOnLimit` now defaults to `true`
2485
+ ### Uploads now reject oversized files by default
2486
2486
 
2487
2487
  The Payload-wide [`upload.abortOnLimit`](../upload/overview#payload-wide-upload-options) option now defaults to `true`. A file that is bigger than the configured `fileSize` limit now returns an HTTP 413 error by default. Previously, the file was truncated to the limit without any error, and the upload continued.
2488
2488
 
@@ -2498,3 +2498,19 @@ export default buildConfig({
2498
2498
  },
2499
2499
  })
2500
2500
  ```
2501
+
2502
+ ### Nodemailer adapter now uses nodemailer 10 ([#18737](https://github.com/payloadcms/payload/pull/18737))
2503
+
2504
+ `@payloadcms/email-nodemailer` now depends on nodemailer 10, which ships its own types. If you pass your own `transport` created with nodemailer 9, it no longer type-checks against the adapter. Upgrade `nodemailer` and remove `@types/nodemailer`:
2505
+
2506
+ ```diff
2507
+ "dependencies": {
2508
+ - "nodemailer": "^9.1.1"
2509
+ + "nodemailer": "^10.1.1"
2510
+ },
2511
+ "devDependencies": {
2512
+ - "@types/nodemailer": "^8.0.0"
2513
+ }
2514
+ ```
2515
+
2516
+ If you use `transportOptions`, or pass no config, no changes are needed.
@@ -0,0 +1,4 @@
1
+ import { getDisplayedFieldValue as display } from '@payloadcms/ui/utilities/getDisplayedFieldValue';
2
+ const result = display(fileSize, field, i18n);
3
+
4
+ //# sourceMappingURL=basic.input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/basic.input.ts"],"sourcesContent":["import { getDisplayedFieldValue as display } from '@payloadcms/ui/utilities/getDisplayedFieldValue'\n\nconst result = display(fileSize, field, i18n)\n"],"names":["getDisplayedFieldValue","display","result","fileSize","field","i18n"],"mappings":"AAAA,SAASA,0BAA0BC,OAAO,QAAQ,kDAAiD;AAEnG,MAAMC,SAASD,QAAQE,UAAUC,OAAOC"}
@@ -0,0 +1,8 @@
1
+ import { getDisplayedFieldValue as display } from '@payloadcms/ui/utilities/getDisplayedFieldValue';
2
+ const result = display({
3
+ cellData: fileSize,
4
+ field: field,
5
+ i18n: i18n
6
+ });
7
+
8
+ //# sourceMappingURL=basic.output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/basic.output.ts"],"sourcesContent":["import { getDisplayedFieldValue as display } from '@payloadcms/ui/utilities/getDisplayedFieldValue'\n\nconst result = display({ cellData: fileSize, field: field, i18n: i18n })\n"],"names":["getDisplayedFieldValue","display","result","cellData","fileSize","field","i18n"],"mappings":"AAAA,SAASA,0BAA0BC,OAAO,QAAQ,kDAAiD;AAEnG,MAAMC,SAASD,QAAQ;IAAEE,UAAUC;IAAUC,OAAOA;IAAOC,MAAMA;AAAK"}
@@ -0,0 +1,3 @@
1
+ import type { Transform } from '../../types.js';
2
+ export declare const migrateDisplayedFieldValueArgs: Transform;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/transforms/migrate-displayed-field-value-args/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAK/C,eAAO,MAAM,8BAA8B,EAAE,SA+D5C,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { Node } from 'ts-morph';
2
+ const moduleName = '@payloadcms/ui/utilities/getDisplayedFieldValue';
3
+ const functionName = 'getDisplayedFieldValue';
4
+ export const migrateDisplayedFieldValueArgs = {
5
+ name: 'migrate-displayed-field-value-args',
6
+ apply: ({ project })=>{
7
+ const filesChanged = [];
8
+ const notes = [];
9
+ for (const file of project.getSourceFiles()){
10
+ const calls = new Set();
11
+ for (const declaration of file.getImportDeclarations()){
12
+ if (declaration.getModuleSpecifierValue() !== moduleName) {
13
+ continue;
14
+ }
15
+ for (const specifier of declaration.getNamedImports()){
16
+ if (specifier.getName() !== functionName || specifier.isTypeOnly()) {
17
+ continue;
18
+ }
19
+ const localName = specifier.getAliasNode() ?? specifier.getNameNode();
20
+ if (Node.isIdentifier(localName)) {
21
+ collectCalls({
22
+ calls,
23
+ localName,
24
+ notes
25
+ });
26
+ }
27
+ }
28
+ const namespace = declaration.getNamespaceImport();
29
+ if (namespace) {
30
+ collectCalls({
31
+ calls,
32
+ isNamespace: true,
33
+ localName: namespace,
34
+ notes
35
+ });
36
+ }
37
+ }
38
+ const original = file.getFullText();
39
+ for (const call of [
40
+ ...calls
41
+ ].sort((a, b)=>b.getStart() - a.getStart())){
42
+ const args = call.getArguments();
43
+ if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
44
+ continue;
45
+ }
46
+ if (args.length !== 3 || args.some(Node.isSpreadElement)) {
47
+ notes.push(`${file.getFilePath()}:${call.getStartLineNumber()}: ${call.getText()} has unsupported arguments; migrate manually.`);
48
+ continue;
49
+ }
50
+ call.replaceWithText(`${call.getExpression().getText()}({ cellData: ${args[0].getText()}, field: ${args[1].getText()}, i18n: ${args[2].getText()} })`);
51
+ }
52
+ if (file.getFullText() !== original) {
53
+ filesChanged.push(file.getFilePath());
54
+ }
55
+ }
56
+ return {
57
+ filesChanged,
58
+ ...notes.length > 0 ? {
59
+ notes
60
+ } : {}
61
+ };
62
+ },
63
+ description: 'Migrates `getDisplayedFieldValue(cellData, field, i18n)` calls imported from `@payloadcms/ui/utilities/getDisplayedFieldValue` to object arguments.'
64
+ };
65
+ function collectCalls({ calls, isNamespace = false, localName, notes }) {
66
+ for (const reference of localName.findReferencesAsNodes()){
67
+ if (reference === localName) {
68
+ continue;
69
+ }
70
+ const parent = reference.getParent();
71
+ const expression = isNamespace ? Node.isPropertyAccessExpression(parent) && parent.getExpression() === reference && parent.getName() === functionName ? parent : undefined : reference;
72
+ if (!expression) {
73
+ continue;
74
+ }
75
+ const call = expression.getParent();
76
+ if (Node.isCallExpression(call) && call.getExpression() === expression) {
77
+ calls.add(call);
78
+ } else {
79
+ notes.push(`${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: ${expression.getText()} is referenced without a direct call; migrate manually.`);
80
+ }
81
+ }
82
+ }
83
+
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/index.ts"],"sourcesContent":["import type { CallExpression, Identifier } from 'ts-morph'\n\nimport { Node } from 'ts-morph'\n\nimport type { Transform } from '../../types.js'\n\nconst moduleName = '@payloadcms/ui/utilities/getDisplayedFieldValue'\nconst functionName = 'getDisplayedFieldValue'\n\nexport const migrateDisplayedFieldValueArgs: Transform = {\n name: 'migrate-displayed-field-value-args',\n apply: ({ project }) => {\n const filesChanged: string[] = []\n const notes: string[] = []\n\n for (const file of project.getSourceFiles()) {\n const calls = new Set<CallExpression>()\n\n for (const declaration of file.getImportDeclarations()) {\n if (declaration.getModuleSpecifierValue() !== moduleName) {\n continue\n }\n\n for (const specifier of declaration.getNamedImports()) {\n if (specifier.getName() !== functionName || specifier.isTypeOnly()) {\n continue\n }\n\n const localName = specifier.getAliasNode() ?? specifier.getNameNode()\n\n if (Node.isIdentifier(localName)) {\n collectCalls({ calls, localName, notes })\n }\n }\n\n const namespace = declaration.getNamespaceImport()\n\n if (namespace) {\n collectCalls({ calls, isNamespace: true, localName: namespace, notes })\n }\n }\n\n const original = file.getFullText()\n\n for (const call of [...calls].sort((a, b) => b.getStart() - a.getStart())) {\n const args = call.getArguments()\n\n if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {\n continue\n }\n\n if (args.length !== 3 || args.some(Node.isSpreadElement)) {\n notes.push(\n `${file.getFilePath()}:${call.getStartLineNumber()}: ${call.getText()} has unsupported arguments; migrate manually.`,\n )\n continue\n }\n\n call.replaceWithText(\n `${call.getExpression().getText()}({ cellData: ${args[0]!.getText()}, field: ${args[1]!.getText()}, i18n: ${args[2]!.getText()} })`,\n )\n }\n\n if (file.getFullText() !== original) {\n filesChanged.push(file.getFilePath())\n }\n }\n\n return { filesChanged, ...(notes.length > 0 ? { notes } : {}) }\n },\n description:\n 'Migrates `getDisplayedFieldValue(cellData, field, i18n)` calls imported from `@payloadcms/ui/utilities/getDisplayedFieldValue` to object arguments.',\n}\n\nfunction collectCalls({\n calls,\n isNamespace = false,\n localName,\n notes,\n}: {\n calls: Set<CallExpression>\n isNamespace?: boolean\n localName: Identifier\n notes: string[]\n}): void {\n for (const reference of localName.findReferencesAsNodes()) {\n if (reference === localName) {\n continue\n }\n\n const parent = reference.getParent()\n const expression = isNamespace\n ? Node.isPropertyAccessExpression(parent) &&\n parent.getExpression() === reference &&\n parent.getName() === functionName\n ? parent\n : undefined\n : reference\n\n if (!expression) {\n continue\n }\n\n const call = expression.getParent()\n\n if (Node.isCallExpression(call) && call.getExpression() === expression) {\n calls.add(call)\n } else {\n notes.push(\n `${reference.getSourceFile().getFilePath()}:${reference.getStartLineNumber()}: ${expression.getText()} is referenced without a direct call; migrate manually.`,\n )\n }\n }\n}\n"],"names":["Node","moduleName","functionName","migrateDisplayedFieldValueArgs","name","apply","project","filesChanged","notes","file","getSourceFiles","calls","Set","declaration","getImportDeclarations","getModuleSpecifierValue","specifier","getNamedImports","getName","isTypeOnly","localName","getAliasNode","getNameNode","isIdentifier","collectCalls","namespace","getNamespaceImport","isNamespace","original","getFullText","call","sort","a","b","getStart","args","getArguments","length","isObjectLiteralExpression","some","isSpreadElement","push","getFilePath","getStartLineNumber","getText","replaceWithText","getExpression","description","reference","findReferencesAsNodes","parent","getParent","expression","isPropertyAccessExpression","undefined","isCallExpression","add","getSourceFile"],"mappings":"AAEA,SAASA,IAAI,QAAQ,WAAU;AAI/B,MAAMC,aAAa;AACnB,MAAMC,eAAe;AAErB,OAAO,MAAMC,iCAA4C;IACvDC,MAAM;IACNC,OAAO,CAAC,EAAEC,OAAO,EAAE;QACjB,MAAMC,eAAyB,EAAE;QACjC,MAAMC,QAAkB,EAAE;QAE1B,KAAK,MAAMC,QAAQH,QAAQI,cAAc,GAAI;YAC3C,MAAMC,QAAQ,IAAIC;YAElB,KAAK,MAAMC,eAAeJ,KAAKK,qBAAqB,GAAI;gBACtD,IAAID,YAAYE,uBAAuB,OAAOd,YAAY;oBACxD;gBACF;gBAEA,KAAK,MAAMe,aAAaH,YAAYI,eAAe,GAAI;oBACrD,IAAID,UAAUE,OAAO,OAAOhB,gBAAgBc,UAAUG,UAAU,IAAI;wBAClE;oBACF;oBAEA,MAAMC,YAAYJ,UAAUK,YAAY,MAAML,UAAUM,WAAW;oBAEnE,IAAItB,KAAKuB,YAAY,CAACH,YAAY;wBAChCI,aAAa;4BAAEb;4BAAOS;4BAAWZ;wBAAM;oBACzC;gBACF;gBAEA,MAAMiB,YAAYZ,YAAYa,kBAAkB;gBAEhD,IAAID,WAAW;oBACbD,aAAa;wBAAEb;wBAAOgB,aAAa;wBAAMP,WAAWK;wBAAWjB;oBAAM;gBACvE;YACF;YAEA,MAAMoB,WAAWnB,KAAKoB,WAAW;YAEjC,KAAK,MAAMC,QAAQ;mBAAInB;aAAM,CAACoB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEC,QAAQ,KAAKF,EAAEE,QAAQ,IAAK;gBACzE,MAAMC,OAAOL,KAAKM,YAAY;gBAE9B,IAAID,KAAKE,MAAM,KAAK,KAAKrC,KAAKsC,yBAAyB,CAACH,IAAI,CAAC,EAAE,GAAG;oBAChE;gBACF;gBAEA,IAAIA,KAAKE,MAAM,KAAK,KAAKF,KAAKI,IAAI,CAACvC,KAAKwC,eAAe,GAAG;oBACxDhC,MAAMiC,IAAI,CACR,GAAGhC,KAAKiC,WAAW,GAAG,CAAC,EAAEZ,KAAKa,kBAAkB,GAAG,EAAE,EAAEb,KAAKc,OAAO,GAAG,6CAA6C,CAAC;oBAEtH;gBACF;gBAEAd,KAAKe,eAAe,CAClB,GAAGf,KAAKgB,aAAa,GAAGF,OAAO,GAAG,aAAa,EAAET,IAAI,CAAC,EAAE,CAAES,OAAO,GAAG,SAAS,EAAET,IAAI,CAAC,EAAE,CAAES,OAAO,GAAG,QAAQ,EAAET,IAAI,CAAC,EAAE,CAAES,OAAO,GAAG,GAAG,CAAC;YAEvI;YAEA,IAAInC,KAAKoB,WAAW,OAAOD,UAAU;gBACnCrB,aAAakC,IAAI,CAAChC,KAAKiC,WAAW;YACpC;QACF;QAEA,OAAO;YAAEnC;YAAc,GAAIC,MAAM6B,MAAM,GAAG,IAAI;gBAAE7B;YAAM,IAAI,CAAC,CAAC;QAAE;IAChE;IACAuC,aACE;AACJ,EAAC;AAED,SAASvB,aAAa,EACpBb,KAAK,EACLgB,cAAc,KAAK,EACnBP,SAAS,EACTZ,KAAK,EAMN;IACC,KAAK,MAAMwC,aAAa5B,UAAU6B,qBAAqB,GAAI;QACzD,IAAID,cAAc5B,WAAW;YAC3B;QACF;QAEA,MAAM8B,SAASF,UAAUG,SAAS;QAClC,MAAMC,aAAazB,cACf3B,KAAKqD,0BAA0B,CAACH,WAChCA,OAAOJ,aAAa,OAAOE,aAC3BE,OAAOhC,OAAO,OAAOhB,eACnBgD,SACAI,YACFN;QAEJ,IAAI,CAACI,YAAY;YACf;QACF;QAEA,MAAMtB,OAAOsB,WAAWD,SAAS;QAEjC,IAAInD,KAAKuD,gBAAgB,CAACzB,SAASA,KAAKgB,aAAa,OAAOM,YAAY;YACtEzC,MAAM6C,GAAG,CAAC1B;QACZ,OAAO;YACLtB,MAAMiC,IAAI,CACR,GAAGO,UAAUS,aAAa,GAAGf,WAAW,GAAG,CAAC,EAAEM,UAAUL,kBAAkB,GAAG,EAAE,EAAES,WAAWR,OAAO,GAAG,uDAAuD,CAAC;QAElK;IACF;AACF"}
@@ -0,0 +1,4 @@
1
+ import * as display from '@payloadcms/ui/utilities/getDisplayedFieldValue';
2
+ const result = display.getDisplayedFieldValue(fileSize, field, i18n);
3
+
4
+ //# sourceMappingURL=namespace.input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/namespace.input.ts"],"sourcesContent":["import * as display from '@payloadcms/ui/utilities/getDisplayedFieldValue'\n\nconst result = display.getDisplayedFieldValue(fileSize, field, i18n)\n"],"names":["display","result","getDisplayedFieldValue","fileSize","field","i18n"],"mappings":"AAAA,YAAYA,aAAa,kDAAiD;AAE1E,MAAMC,SAASD,QAAQE,sBAAsB,CAACC,UAAUC,OAAOC"}
@@ -0,0 +1,8 @@
1
+ import * as display from '@payloadcms/ui/utilities/getDisplayedFieldValue';
2
+ const result = display.getDisplayedFieldValue({
3
+ cellData: fileSize,
4
+ field: field,
5
+ i18n: i18n
6
+ });
7
+
8
+ //# sourceMappingURL=namespace.output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/namespace.output.ts"],"sourcesContent":["import * as display from '@payloadcms/ui/utilities/getDisplayedFieldValue'\n\nconst result = display.getDisplayedFieldValue({ cellData: fileSize, field: field, i18n: i18n })\n"],"names":["display","result","getDisplayedFieldValue","cellData","fileSize","field","i18n"],"mappings":"AAAA,YAAYA,aAAa,kDAAiD;AAE1E,MAAMC,SAASD,QAAQE,sBAAsB,CAAC;IAAEC,UAAUC;IAAUC,OAAOA;IAAOC,MAAMA;AAAK"}
@@ -0,0 +1,4 @@
1
+ import { getDisplayedFieldValue } from './local-display.js';
2
+ const result = getDisplayedFieldValue(fileSize, field, i18n);
3
+
4
+ //# sourceMappingURL=no-match.input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/no-match.input.ts"],"sourcesContent":["import { getDisplayedFieldValue } from './local-display.js'\n\nconst result = getDisplayedFieldValue(fileSize, field, i18n)\n"],"names":["getDisplayedFieldValue","result","fileSize","field","i18n"],"mappings":"AAAA,SAASA,sBAAsB,QAAQ,qBAAoB;AAE3D,MAAMC,SAASD,uBAAuBE,UAAUC,OAAOC"}
@@ -0,0 +1,4 @@
1
+ import { getDisplayedFieldValue } from './local-display.js';
2
+ const result = getDisplayedFieldValue(fileSize, field, i18n);
3
+
4
+ //# sourceMappingURL=no-match.output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/transforms/migrate-displayed-field-value-args/no-match.output.ts"],"sourcesContent":["import { getDisplayedFieldValue } from './local-display.js'\n\nconst result = getDisplayedFieldValue(fileSize, field, i18n)\n"],"names":["getDisplayedFieldValue","result","fileSize","field","i18n"],"mappings":"AAAA,SAASA,sBAAsB,QAAQ,qBAAoB;AAE3D,MAAMC,SAASD,uBAAuBE,UAAUC,OAAOC"}
@@ -3,7 +3,7 @@
3
3
  * the monorepo (this one included) is versioned and published in lockstep, so
4
4
  * the dist-tag the codemod was invoked under (`npx @payloadcms/codemod@beta`)
5
5
  * surfaces as its own version's prerelease id. Follow that train; fall back to
6
- * canary for stable or local dev builds that carry no prerelease id.
6
+ * beta for stable or local dev builds that carry no prerelease id.
7
7
  */
8
8
  export declare function resolveDefaultTag(version?: string | undefined): string;
9
9
  //# sourceMappingURL=defaultTag.d.ts.map
@@ -2,13 +2,13 @@ import { readFileSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import semver from 'semver';
5
- /** Dist-tag used when neither --tag nor the running version implies one. */ const FALLBACK_TAG = 'canary';
5
+ /** Dist-tag used when neither --tag nor the running version implies one. */ const FALLBACK_TAG = 'beta';
6
6
  /**
7
7
  * Default Payload dist-tag when the caller did not pass --tag. Every package in
8
8
  * the monorepo (this one included) is versioned and published in lockstep, so
9
9
  * the dist-tag the codemod was invoked under (`npx @payloadcms/codemod@beta`)
10
10
  * surfaces as its own version's prerelease id. Follow that train; fall back to
11
- * canary for stable or local dev builds that carry no prerelease id.
11
+ * beta for stable or local dev builds that carry no prerelease id.
12
12
  */ export function resolveDefaultTag(version = readOwnVersion()) {
13
13
  const id = version ? semver.prerelease(version)?.[0] : undefined;
14
14
  return typeof id === 'string' ? id : FALLBACK_TAG;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/upgrade/defaultTag.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport semver from 'semver'\n\n/** Dist-tag used when neither --tag nor the running version implies one. */\nconst FALLBACK_TAG = 'canary'\n\n/**\n * Default Payload dist-tag when the caller did not pass --tag. Every package in\n * the monorepo (this one included) is versioned and published in lockstep, so\n * the dist-tag the codemod was invoked under (`npx @payloadcms/codemod@beta`)\n * surfaces as its own version's prerelease id. Follow that train; fall back to\n * canary for stable or local dev builds that carry no prerelease id.\n */\nexport function resolveDefaultTag(version: string | undefined = readOwnVersion()): string {\n const id = version ? semver.prerelease(version)?.[0] : undefined\n return typeof id === 'string' ? id : FALLBACK_TAG\n}\n\nfunction readOwnVersion(): string | undefined {\n try {\n const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json')\n return JSON.parse(readFileSync(pkgPath, 'utf8')).version as string\n } catch {\n return undefined\n }\n}\n"],"names":["readFileSync","dirname","resolve","fileURLToPath","semver","FALLBACK_TAG","resolveDefaultTag","version","readOwnVersion","id","prerelease","undefined","pkgPath","url","JSON","parse"],"mappings":"AAAA,SAASA,YAAY,QAAQ,UAAS;AACtC,SAASC,OAAO,EAAEC,OAAO,QAAQ,YAAW;AAC5C,SAASC,aAAa,QAAQ,WAAU;AACxC,OAAOC,YAAY,SAAQ;AAE3B,0EAA0E,GAC1E,MAAMC,eAAe;AAErB;;;;;;CAMC,GACD,OAAO,SAASC,kBAAkBC,UAA8BC,gBAAgB;IAC9E,MAAMC,KAAKF,UAAUH,OAAOM,UAAU,CAACH,UAAU,CAAC,EAAE,GAAGI;IACvD,OAAO,OAAOF,OAAO,WAAWA,KAAKJ;AACvC;AAEA,SAASG;IACP,IAAI;QACF,MAAMI,UAAUV,QAAQD,QAAQE,cAAc,YAAYU,GAAG,IAAI,MAAM,MAAM;QAC7E,OAAOC,KAAKC,KAAK,CAACf,aAAaY,SAAS,SAASL,OAAO;IAC1D,EAAE,OAAM;QACN,OAAOI;IACT;AACF"}
1
+ {"version":3,"sources":["../../src/upgrade/defaultTag.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport semver from 'semver'\n\n/** Dist-tag used when neither --tag nor the running version implies one. */\nconst FALLBACK_TAG = 'beta'\n\n/**\n * Default Payload dist-tag when the caller did not pass --tag. Every package in\n * the monorepo (this one included) is versioned and published in lockstep, so\n * the dist-tag the codemod was invoked under (`npx @payloadcms/codemod@beta`)\n * surfaces as its own version's prerelease id. Follow that train; fall back to\n * beta for stable or local dev builds that carry no prerelease id.\n */\nexport function resolveDefaultTag(version: string | undefined = readOwnVersion()): string {\n const id = version ? semver.prerelease(version)?.[0] : undefined\n return typeof id === 'string' ? id : FALLBACK_TAG\n}\n\nfunction readOwnVersion(): string | undefined {\n try {\n const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json')\n return JSON.parse(readFileSync(pkgPath, 'utf8')).version as string\n } catch {\n return undefined\n }\n}\n"],"names":["readFileSync","dirname","resolve","fileURLToPath","semver","FALLBACK_TAG","resolveDefaultTag","version","readOwnVersion","id","prerelease","undefined","pkgPath","url","JSON","parse"],"mappings":"AAAA,SAASA,YAAY,QAAQ,UAAS;AACtC,SAASC,OAAO,EAAEC,OAAO,QAAQ,YAAW;AAC5C,SAASC,aAAa,QAAQ,WAAU;AACxC,OAAOC,YAAY,SAAQ;AAE3B,0EAA0E,GAC1E,MAAMC,eAAe;AAErB;;;;;;CAMC,GACD,OAAO,SAASC,kBAAkBC,UAA8BC,gBAAgB;IAC9E,MAAMC,KAAKF,UAAUH,OAAOM,UAAU,CAACH,UAAU,CAAC,EAAE,GAAGI;IACvD,OAAO,OAAOF,OAAO,WAAWA,KAAKJ;AACvC;AAEA,SAASG;IACP,IAAI;QACF,MAAMI,UAAUV,QAAQD,QAAQE,cAAc,YAAYU,GAAG,IAAI,MAAM,MAAM;QAC7E,OAAOC,KAAKC,KAAK,CAACf,aAAaY,SAAS,SAASL,OAAO;IAC1D,EAAE,OAAM;QACN,OAAOI;IACT;AACF"}
@@ -30,8 +30,8 @@ Run this from the project root:
30
30
 
31
31
  ${command} upgrade run
32
32
 
33
- By default it resolves Payload from the dist-tag this CLI was installed under (\`beta\` when you
34
- ran \`@payloadcms/codemod@beta\`, else \`canary\`). Add \`--tag <dist-tag>\` to pin a specific one.
33
+ By default it resolves Payload from the dist-tag this CLI was installed under (its own
34
+ prerelease id, else \`beta\`). Add \`--tag <dist-tag>\` to pin a specific one.
35
35
  This pins the Payload packages to v4, removes conflicting overrides, writes the toolchain
36
36
  floors, installs, and runs the AST transforms. Read its report:
37
37
  - It prints the resolved payload version, the required Next target, and the path to the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/upgrade/prompt.ts"],"sourcesContent":["/**\n * Render the overarching agent prompt for a full Payload v3 -> v4 upgrade,\n * Next.js 16 included. The prompt is a thin orchestrator: it sequences the\n * gates, runs this package's `upgrade` command for the deterministic slice,\n * delegates Next.js to Next's own agent workflow, and points at the migration\n * guide for the judgment work rather than restating any of it. The text is\n * project-agnostic (the agent runs it from the project root). `command` is the\n * token that re-invokes this CLI, so a local checkout points followers at the\n * local build instead of the published package. Pure: the caller prints it.\n */\nexport function renderUpgradePrompt({\n command = 'npx @payloadcms/codemod',\n}: { command?: string } = {}): string {\n return `You are upgrading this project from Payload v3 to v4, Next.js 16 included.\n\nGround rule: resolution over intent. A step is done only when it installs, builds, and boots.\nNever report a task complete because the edit looks right. Confirm it. A silent gap reads as\n\"checked and fine\".\n\n## 0. Preconditions\n- Work on a clean git tree or a fresh branch.\n- Use Node >=24.15 (the exact engines floor is printed by the command in step 2).\n\n## 1. Pre-upgrade migrations (do these on v3, before touching versions)\nSome migrations need the v3 tooling that v4 deletes, so they MUST run before the upgrade:\n- Slate richtext is removed in v4. Migrate Slate -> Lexical while still on v3; the migration path\n is gone once the packages are pinned to v4.\n\nDo not proceed to step 2 until these are complete and committed.\n\n## 2. Payload mechanical slice\nRun this from the project root:\n\n ${command} upgrade run\n\nBy default it resolves Payload from the dist-tag this CLI was installed under (\\`beta\\` when you\nran \\`@payloadcms/codemod@beta\\`, else \\`canary\\`). Add \\`--tag <dist-tag>\\` to pin a specific one.\nThis pins the Payload packages to v4, removes conflicting overrides, writes the toolchain\nfloors, installs, and runs the AST transforms. Read its report:\n- It prints the resolved payload version, the required Next target, and the path to the\n bundled runbook and migration guide. Use those paths below.\n- The unmet \\`@payloadcms/next\\` peer against Next 15 is EXPECTED at this point. Do NOT downgrade\n payload to satisfy it; step 3 resolves it by upgrading Next.\n- Do not hand-edit the versions it pinned.\n\n## 3. Next.js 16 (delegate to Next's workflow)\nPayload v4 requires Next 16. The command above did not touch Next. Run Next's own recommended\nagent workflow, which bumps Next + React and migrates the code as one unit:\n<https://nextjs.org/docs/app/guides/upgrading/version-16#use-an-ai-agent-recommended>\n\nIn short: run \\`npx @next/codemod@canary upgrade latest\\`, then\n\\`npx @next/codemod@canary next-async-request-api .\\`, and follow Next's AGENTS.md workflow for\nthe rest. Keep the result within the Next target range printed in step 2's report. Reinstall\nafterward so the \\`@payloadcms/next\\` peer resolves.\n\n## 4. Regenerate generated files\n\\`payload generate:types && payload generate:importmap\\`. Never hand-edit \\`payload-types.ts\\` or\n\\`importMap.js\\`; regenerate them.\n\n## 5. Fix-forward judgment work (against the now-v4 tree)\nThese depend on the v4 APIs and types existing, so they follow the upgrade. Open the bundled\nmigration guide (\\`v4.mdx\\`, path printed in step 2) and the bundled runbook\n(\\`runbook/payload-v4-upgrade.md\\`) and work the items they list: SCSS -> CSS and \\`--theme-*\\` ->\n\\`--color-*\\` token renames, Lexical union type-narrowing, jobs DB migrations, strict cron parsing,\nand anything flagged in the per-transform notes. Consult the guide for each; do not guess from\nmemory.\n\n## 6. Verify\n- \\`tsc --noEmit\\` (delete \\`tsconfig.tsbuildinfo\\` first; set \\`checkJs: true\\` so \\`importMap.js\\` is covered).\n- Run integration and e2e tests separately.\n- Build.\n- Confirm native modules (e.g. \\`sharp\\`) still load.\n\n## 7. Report\nDistinguish verified, cannot-confirm, and broken. Do not call the upgrade complete while any\ngate fails or any generated file is stale.\n`\n}\n"],"names":["renderUpgradePrompt","command"],"mappings":"AAAA;;;;;;;;;CASC,GACD,OAAO,SAASA,oBAAoB,EAClCC,UAAU,yBAAyB,EACd,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;IAoBN,EAAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2Cd,CAAC;AACD"}
1
+ {"version":3,"sources":["../../src/upgrade/prompt.ts"],"sourcesContent":["/**\n * Render the overarching agent prompt for a full Payload v3 -> v4 upgrade,\n * Next.js 16 included. The prompt is a thin orchestrator: it sequences the\n * gates, runs this package's `upgrade` command for the deterministic slice,\n * delegates Next.js to Next's own agent workflow, and points at the migration\n * guide for the judgment work rather than restating any of it. The text is\n * project-agnostic (the agent runs it from the project root). `command` is the\n * token that re-invokes this CLI, so a local checkout points followers at the\n * local build instead of the published package. Pure: the caller prints it.\n */\nexport function renderUpgradePrompt({\n command = 'npx @payloadcms/codemod',\n}: { command?: string } = {}): string {\n return `You are upgrading this project from Payload v3 to v4, Next.js 16 included.\n\nGround rule: resolution over intent. A step is done only when it installs, builds, and boots.\nNever report a task complete because the edit looks right. Confirm it. A silent gap reads as\n\"checked and fine\".\n\n## 0. Preconditions\n- Work on a clean git tree or a fresh branch.\n- Use Node >=24.15 (the exact engines floor is printed by the command in step 2).\n\n## 1. Pre-upgrade migrations (do these on v3, before touching versions)\nSome migrations need the v3 tooling that v4 deletes, so they MUST run before the upgrade:\n- Slate richtext is removed in v4. Migrate Slate -> Lexical while still on v3; the migration path\n is gone once the packages are pinned to v4.\n\nDo not proceed to step 2 until these are complete and committed.\n\n## 2. Payload mechanical slice\nRun this from the project root:\n\n ${command} upgrade run\n\nBy default it resolves Payload from the dist-tag this CLI was installed under (its own\nprerelease id, else \\`beta\\`). Add \\`--tag <dist-tag>\\` to pin a specific one.\nThis pins the Payload packages to v4, removes conflicting overrides, writes the toolchain\nfloors, installs, and runs the AST transforms. Read its report:\n- It prints the resolved payload version, the required Next target, and the path to the\n bundled runbook and migration guide. Use those paths below.\n- The unmet \\`@payloadcms/next\\` peer against Next 15 is EXPECTED at this point. Do NOT downgrade\n payload to satisfy it; step 3 resolves it by upgrading Next.\n- Do not hand-edit the versions it pinned.\n\n## 3. Next.js 16 (delegate to Next's workflow)\nPayload v4 requires Next 16. The command above did not touch Next. Run Next's own recommended\nagent workflow, which bumps Next + React and migrates the code as one unit:\n<https://nextjs.org/docs/app/guides/upgrading/version-16#use-an-ai-agent-recommended>\n\nIn short: run \\`npx @next/codemod@canary upgrade latest\\`, then\n\\`npx @next/codemod@canary next-async-request-api .\\`, and follow Next's AGENTS.md workflow for\nthe rest. Keep the result within the Next target range printed in step 2's report. Reinstall\nafterward so the \\`@payloadcms/next\\` peer resolves.\n\n## 4. Regenerate generated files\n\\`payload generate:types && payload generate:importmap\\`. Never hand-edit \\`payload-types.ts\\` or\n\\`importMap.js\\`; regenerate them.\n\n## 5. Fix-forward judgment work (against the now-v4 tree)\nThese depend on the v4 APIs and types existing, so they follow the upgrade. Open the bundled\nmigration guide (\\`v4.mdx\\`, path printed in step 2) and the bundled runbook\n(\\`runbook/payload-v4-upgrade.md\\`) and work the items they list: SCSS -> CSS and \\`--theme-*\\` ->\n\\`--color-*\\` token renames, Lexical union type-narrowing, jobs DB migrations, strict cron parsing,\nand anything flagged in the per-transform notes. Consult the guide for each; do not guess from\nmemory.\n\n## 6. Verify\n- \\`tsc --noEmit\\` (delete \\`tsconfig.tsbuildinfo\\` first; set \\`checkJs: true\\` so \\`importMap.js\\` is covered).\n- Run integration and e2e tests separately.\n- Build.\n- Confirm native modules (e.g. \\`sharp\\`) still load.\n\n## 7. Report\nDistinguish verified, cannot-confirm, and broken. Do not call the upgrade complete while any\ngate fails or any generated file is stale.\n`\n}\n"],"names":["renderUpgradePrompt","command"],"mappings":"AAAA;;;;;;;;;CASC,GACD,OAAO,SAASA,oBAAoB,EAClCC,UAAU,yBAAyB,EACd,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;IAoBN,EAAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2Cd,CAAC;AACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/codemod",
3
- "version": "4.0.0-beta.0",
3
+ "version": "4.0.0-beta.2",
4
4
  "description": "Codemods for migrating Payload projects across versions.",
5
5
  "homepage": "https://payloadcms.com",
6
6
  "repository": {