Skip to content

feat!: move storage adapters to storage property instead of plugins for early initialisation#16596

Merged
paulpopus merged 15 commits into
mainfrom
feat/move-storage-adapters-to-their-own-config-for-early-initilisation
May 20, 2026
Merged

feat!: move storage adapters to storage property instead of plugins for early initialisation#16596
paulpopus merged 15 commits into
mainfrom
feat/move-storage-adapters-to-their-own-config-for-early-initilisation

Conversation

@paulpopus

@paulpopus paulpopus commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Introduces a storage config key for Payload, letting storage adapters (@payloadcms/storage-s3, -azure, -gcs, -r2, -vercel-blob, -uploadthing) be declared at the top level of the config rather than inside plugins
  • Storage adapters now return a StorageAdapter object ({ name, collections, init }) instead of a Plugin function; buildConfig calls adapter.init(config) before any plugins run, guaranteeing upload hooks are wired after plugins execute
  • Adds a migrate-storage-adapters-to-config codemod to automate migration: npx @payloadcms/codemod --transform migrate-storage-adapters-to-config

Migration

Existing plugins usage continues to work unchanged. To adopt the new API, move storage adapters out of plugins and into storage:

// Before
export default buildConfig({
  plugins: [
    s3Storage({ bucket: '...', collections: { media: true }, config: { region: '...' } }),
    otherPlugin(),
  ],
})

// After
export default buildConfig({
  plugins: [otherPlugin()],
  storage: [
    s3Storage({ bucket: '...', collections: { media: true }, config: { region: '...' } }),
  ],
})

Run the codemod to automate this:

npx @payloadcms/codemod --transform migrate-storage-adapters-to-config

@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 989.31 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.41 MB ✅ -92 B (-0.0%)
packages/payload/meta_shared.json esbuild/exports/shared.js 192.60 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 304.12 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.24 MB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 16.11 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████▌ }}}$ 82.0%, 807.52 KB
dist/views/Version ${{\color{Goldenrod}{ █▎ }}}$ 5.2%, 51.48 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 21.71 KB
dist/views/Document ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 16.66 KB
dist/views/List ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 15.39 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 10.24 KB
dist/views/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 9.90 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.17 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.13 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.06 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 4.71 KB
dist/views/Login ${{\color{Goldenrod}{ }}}$ 0.4%, 4.40 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.4%, 3.53 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 3.13 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.3%, 2.81 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.2%, 2.40 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.2%, 2.02 KB
dist/views/Logout ${{\color{Goldenrod}{ }}}$ 0.2%, 1.94 KB
dist/views/Verify ${{\color{Goldenrod}{ }}}$ 0.1%, 1.29 KB
dist/views/NotFound ${{\color{Goldenrod}{ }}}$ 0.1%, 1.21 KB
(other) ${{\color{Goldenrod}{ ████▌ }}}$ 18.0%, 177.11 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████ }}}$ 68.4%, 959.38 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 44.07 KB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 40.23 KB
dist/versions/migrations ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 18.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.63 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.66 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.40 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 13.13 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.63 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.57 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.53 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.44 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.92 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.80 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.77 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.65 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.54 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 6.23 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.50 KB
dist/queues/config ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
(other) ${{\color{Goldenrod}{ ███████▉ }}}$ 31.6%, 443.84 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.5%, 150.12 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 10.57 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.54 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.37 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 943 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.3%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/fields/getFieldPaths.js ${{\color{Goldenrod}{ }}}$ 0.3%, 485 B
dist/utilities/appendDateTimezoneSelectFields.js ${{\color{Goldenrod}{ }}}$ 0.2%, 451 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.2%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.2%, 413 B
(other) ${{\color{Goldenrod}{ █████▏ }}}$ 20.5%, 38.70 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███ }}}$ 12.4%, 37.36 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▊ }}}$ 11.4%, 34.16 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▋ }}}$ 10.9%, 32.88 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▎ }}}$ 9.0%, 27.16 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.81 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 16.58 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 14.09 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 9.39 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.79 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 8.36 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.40 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 7.29 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.84 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.6%, 263.53 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████▊ }}}$ 47.0%, 579.80 KB
dist/elements/Hierarchy ${{\color{Goldenrod}{ ▉ }}}$ 3.5%, 43.71 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 28.05 KB
dist/elements/Table ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 19.18 KB
dist/views/HierarchyList ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 18.70 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 17.38 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 17.24 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.92 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.48 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.11 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 14.39 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.29 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.00 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.99 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.84 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.75 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.04 KB
dist/elements/RelationshipTable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.88 KB
dist/elements/Upload ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.62 KB
(other) ${{\color{Goldenrod}{ █████████████▎ }}}$ 53.0%, 653.34 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████ }}}$ 20.2%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▎ }}}$ 17.1%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▍ }}}$ 9.8%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▏ }}}$ 8.6%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █▏ }}}$ 4.9%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▏ }}}$ 4.9%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 734 B
dist/elements/Translation ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 399 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 159 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 146 B
(other) ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.8%, 12.36 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@paulpopus paulpopus changed the title feat: move storage adapters to their own config for early initilisation feat!: move storage adapters to their own config for early initilisation May 13, 2026
@paulpopus paulpopus changed the title feat!: move storage adapters to their own config for early initilisation feat!: move storage adapters to storageAdapters property instead of plugins for early initialisation May 13, 2026
@paulpopus paulpopus marked this pull request as ready for review May 14, 2026 17:05
@paulpopus paulpopus requested a review from denolfe as a code owner May 14, 2026 17:05
@paulpopus paulpopus changed the title feat!: move storage adapters to storageAdapters property instead of plugins for early initialisation feat!: move storage adapters to storage property instead of plugins for early initialisation May 18, 2026

@denolfe denolfe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to do some deeper thinking on improving this pattern. I have concerns about whether this actually solves the pain point we want. The scenario I'm thinking of is:

  • You have a plugin that adds some internal collections my-plugin-coll-1
  • You want the plugin to use a specific storage adapter

Right now, your storage adapter needs to know about the existence of this plugin's internal collection, which I think is quite clunky and not ideal.

I'd like us to consider having some sort of "registration" of storage adapters that can then be referenced by the plugin.

Let's get a discussion on the books for this.

@paulpopus paulpopus merged commit d204dab into main May 20, 2026
326 of 328 checks passed
@paulpopus paulpopus deleted the feat/move-storage-adapters-to-their-own-config-for-early-initilisation branch May 20, 2026 12:19

@DanRibbens DanRibbens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs docs in docs/migration-guide/v4.mdx

denolfe added a commit that referenced this pull request Jun 4, 2026
Migrate templates to 4.x APIs via @payloadcms/codemod plus manual fixes:
- Move storage adapters from plugins to the top-level storage array (#16596)
- Rewrite removed @payloadcms/next/{rsc,client} subpaths to @payloadcms/ui in import maps
- Replace bare src/ imports with the @/ path alias
- Drop the slug generic on Pages/Posts so defaultPopulate stays assignable to the collections array
denolfe added a commit that referenced this pull request Jun 5, 2026
# Overview

The `build-and-test-templates` CI job was disabled while 4.x breaking
changes landed, so the templates drifted and no longer build against
current core packages. This updates each template to the new 4.x APIs so
they build again.

CI for the re-enabled job:
[#16880](#16880).

## Key Changes

- **Storage adapters moved from `plugins` to `storage`**
- 4.x initializes storage adapters from a top-level `storage` array
instead of `plugins` (#16596). `vercelBlobStorage` / `r2Storage` calls
were relocated in `with-vercel-mongodb`, `with-vercel-postgres`,
`with-vercel-website`, and `with-cloudflare-d1`. This was the source of
the `TypeError: plugin is not a function` failures. Applied with
`@payloadcms/codemod`'s `migrate-storage-adapters-to-config`.

- **Import maps point at `@payloadcms/ui` instead of removed
`@payloadcms/next` subpaths**
- The `@payloadcms/next/rsc` and `@payloadcms/next/client` subpaths were
removed; generated import maps now reference `@payloadcms/ui/rsc` and
`@payloadcms/ui`. Applied with `@payloadcms/codemod`'s
`migrate-next-subpath-exports` across all templates.

- **Bare `src/` imports replaced with the `@/` path alias**
- Website-based templates imported from `src/...`, which no longer
resolves under Turbopack. Switched to the configured `@/` alias (maps to
`./src/*`).

- **Slug generic dropped on `Pages` / `Posts` (TypeScript 6 regression
workaround)**
- The documented `CollectionConfig<'pages'>` + `defaultPopulate` pattern
compiled under TypeScript 5.7 (3.x) but not under TypeScript 6.0.3 (the
#16692 bump): the generated select interface is no longer assignable to
the `SelectType` that the `collections` array element expects. Dropping
the generic lets `defaultPopulate` fall back to `SelectType` so the
templates build, at the cost of field-name checking on
`defaultPopulate`. This is a core type regression that affects any 4.x
project using the pattern under TS 6, so the real fix belongs in core;
the templates carry a commented workaround to be reverted once that
lands.

## Design Decisions

Most changes were applied with `@payloadcms/codemod` rather than by
hand, so the same transforms users will run produce the same result.
Import maps are left as the minimal subpath rewrite rather than a full
local regeneration, since `payload build` regenerates them during the
build.

Scope is template builds. All six matrix templates build with packed
local packages, verified locally via `script:pack --all` +
`build-template-with-local-pkgs`.

The remaining E2E failures on `blank` and `website`
(`chunk.reason.enqueueModel is not a function` when loading the admin
under `next dev`) are a pre-existing runtime issue, present before this
change and unrelated to the build fixes. Only the workspace templates
(`blank`, `website`, `ecommerce`) run E2E in this job; `pnpm --filter`
does not match the non-workspace templates, so their E2E and int steps
currently no-op.


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1215426886664378
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants