Skip to content

refactor!: consolidate initReq across framework adapters - #17690

Merged
jacobsfletch merged 23 commits into
mainfrom
refactor/consolidate-initreq
Sep 23, 2026
Merged

jacobsfletch merged 23 commits into
mainfrom
refactor/consolidate-initreq

Conversation

@jacobsfletch

@jacobsfletch jacobsfletch commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Consolidates initReq in payload core so every framework adapter uses the same request initialization behavior.

initReq was originally designed around the Next.js rendering model. As support for TanStack Start was added, its Next.js-specific assumptions led us to maintain a separate TanStack Start implementation.

Moving the shared behavior into payload provides one canonical implementation while keeping framework-specific concerns at the adapter boundary. Each framework adapter is responsible for:

  • Supplying its ServerAdapter
  • Supplying framework-specific request context, e.g. the request URL
  • Supplying a request-scoped cache, when available (see below)

Caching

This PR does not introduce a new caching model. The previous implementation already used React’s request-scoped cache. This PR preserves that behavior and exposes a contract that framework adapters can follow.

When supplied, the cache reuses state across initReq calls, such as layout and page invocations within the same request. This includes:

  • Shared getPayload instance, i18n, and authentication
  • Locale resolution for the same user and requested locale
  • Complete request results for repeated calls with the same key and overrides

Each framework adapter owns its own caching behavior:

  • Next.js continues to use React’s request-scoped cache within an RSC render.
  • TanStack Start retains its existing uncached behavior, executing each initReq call independently while relying on getPayload’s module-level instance cache.

In the future, it's worth further exploring if TanStack can fully support caching the req. Wiring that context into the adapter remains out of scope but should be evaluated as a separate performance optimization.

Breaking Changes

Payload supplies an initialized PayloadRequest to custom endpoints, server-rendered custom admin views, and other supported extension points that require request state. Consumers should use the initialized request supplied by Payload at supported extension points instead of initializing their own. initReq is a low-level integration point intended for framework adapters and may change without notice.

Framework adapter authors can import the canonical implementation from payload, with the understanding that it remains internal:

Old source New source
@payloadcms/ui/utilities/initReq payload/internal
- import { initReq } from '@payloadcms/ui/utilities/initReq'
+ import { initReq } from 'payload/internal'

RootLayout from @payloadcms/ui/layouts now accepts an adapter-owned initReq function instead of a serverAdapter:

<RootLayout
- serverAdapter={serverAdapter}
+ initReq={initReq}
  {...props}
/>

@github-actions

github-actions Bot commented Aug 7, 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 215.24 KB ⚠️ +469 B (+0.2%)
packages/payload/meta_index.json esbuild/index.js 1.86 MB ✅ No change
packages/payload/meta_shared.json esbuild/exports/shared.js 553.17 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 285.41 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 36.54 KB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.95 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}{ ████████████████████████▋ }}}$ 98.8%, 210.74 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 718 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.2%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.2%, 520 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.1%, 315 B
dist/utilities/initReq.js ${{\color{Goldenrod}{ }}}$ 0.1%, 306 B
dist/utilities/selectiveCache.js ${{\color{Goldenrod}{ }}}$ 0.1%, 263 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

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

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████ }}}$ 72.1%, 1.33 MB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 53.94 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 46.15 KB
dist/auth/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.84 KB
dist/globals/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.03 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.02 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 14.29 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 13.65 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.88 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.82 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.50 KB
dist/cli/commands ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.92 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.67 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.38 KB
dist/database/migrations ${{\color{Goldenrod}{ }}}$ 0.4%, 7.99 KB
dist/index.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.77 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ }}}$ 0.4%, 7.66 KB
dist/utilities/entityInputSchema ${{\color{Goldenrod}{ }}}$ 0.4%, 7.34 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 7.32 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.15 KB
(other) ${{\color{Goldenrod}{ ██████▉ }}}$ 27.9%, 516.19 KB

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

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████████▏ }}}$ 88.8%, 486.64 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 10.79 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 5.83 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 4.34 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.52 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.42 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.79 KB
dist/config/client.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.56 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.42 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ }}}$ 0.2%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ }}}$ 0.2%, 939 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.2%, 850 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.1%, 794 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.1%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.1%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.1%, 651 B
dist/utilities/fieldPath.js ${{\color{Goldenrod}{ }}}$ 0.1%, 639 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.1%, 632 B
(other) ${{\color{Goldenrod}{ ██▊ }}}$ 11.2%, 61.65 KB

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

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▎ }}}$ 13.2%, 37.20 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▉ }}}$ 11.8%, 33.21 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.7%, 33.01 KB
dist/features/table ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 27.18 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.7%, 18.82 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 17.45 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 14.24 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.9%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▉ }}}$ 3.5%, 10.02 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.3%, 9.43 KB
dist/features/converters ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 8.40 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.14 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.67 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.28 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}{ ▎ }}}$ 1.0%, 2.88 KB
dist/lexical/nodes ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.66 KB
(other) ${{\color{Goldenrod}{ █████████████████████▋ }}}$ 86.8%, 245.00 KB

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

Path Size
dist/exports/client ${{\color{Goldenrod}{ █████████████████████████ }}}$ 100.0%, 26.90 KB

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

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.5%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▌ }}}$ 14.5%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.3%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.2%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/layouts/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 230 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 158 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
(other) ${{\color{Goldenrod}{ █████████████████▍ }}}$ 69.5%, 12.68 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.

# Conflicts:
#	package.json
#	packages/tanstack-start/src/layouts/Root/getLayoutData.ts
#	packages/tanstack-start/src/utilities/initReq.server.ts
#	packages/ui/src/layouts/Root/index.tsx
#	packages/ui/src/utilities/initReq.ts
#	vitest.config.ts
@jacobsfletch jacobsfletch changed the title refactor!: consolidate initReq in payload refactor!: consolidate initReq across framework adapters Sep 22, 2026
const { responseHeaders, user } = await executeAuthStrategies({
headers,
payload,
export const initReq = ({ serverAdapter = tanstackServerAdapter, ...args }: TanStackInitReqArgs) =>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is where the majority of the consolidation surfaces.

@jacobsfletch
jacobsfletch marked this pull request as ready for review September 22, 2026 21:04
@jacobsfletch
jacobsfletch merged commit d1deb35 into main Sep 23, 2026
283 checks passed
@jacobsfletch
jacobsfletch deleted the refactor/consolidate-initreq branch September 23, 2026 20:24
jacobsfletch added a commit that referenced this pull request Sep 25, 2026
Follow up to #17690

Consolidates `PayloadRequest` creation around a canonical
`createPayloadRequest` function.

Payload currently exposes three overlapping paths for constructing a
`PayloadRequest`:

- `createLocalReq` for local operations
- `createPayloadRequest` to convert incoming `WebRequest` objects
- `initReq` for admin rendering

`createPayloadRequest` and `initReq` ultimately delegate to
`createLocalReq`, but their names obscure that relationship.
`createPayloadRequest` sounds like the general primitive despite being
specific to Web requests, while `initReq` does not communicate that it
prepares the complete admin context _in addition to_ creating the req.

The new names describe each functions purpose much more clearly to
remove any ambiguity or confusion.

## Breaking Changes

Consumers that directly import request helpers, internal admin context
types, or provide a custom Root layout adapter must migrate the renamed
APIs.

| Old name | New name | Reasoning | 
| --- | --- | --- |
| `createLocalReq` | `createPayloadRequest` | Not just meant for the
Local API |
| `createPayloadRequest` | `createPayloadRequestFromWebRequest` |
Verbose but explicit |
| `initReq` | `initAdminContext` | Kept "init" to signal that this is
called at the start of the request lifecycle |

Types have also been renamed to match:

| Old name | New name |
| --- | --- |
| `InitReqArgs` | `InitAdminContextArgs` | 
| `InitReqResult` | `AdminContext` | 
| `InitReqCache` | `AdminContextCache` | 
| `InitReqPartialResult` | `PartialAdminContext` | 

Local request creation now accepts `payload` in the options object:

```diff
-import { createLocalReq } from 'payload'
+import { createPayloadRequest } from 'payload'

-const req = await createLocalReq(options, payload)
+const req = await createPayloadRequest({ ...options, payload })
```

Web request conversion uses its role-specific name:

```diff
-import { createPayloadRequest } from 'payload'
+import { createPayloadRequestFromWebRequest } from 'payload'

-const req = await createPayloadRequest({ config, request })
+const req = await createPayloadRequestFromWebRequest({ config, request })
```

Custom Root layout adapters must rename their injected admin context
callback:

```diff
 <RootLayout
-  initReq={initReq}
+  initAdminContext={initAdminContext}
 />
```

The migration guide documents options that require manual handling,
including complex expressions and private imports of
`CreateLocalReqOptions`.

#### Codemod

To migrate automatically, there's a codemod for this change available by
running:

```bash
npx @payloadcms/codemod --transform migrate-payload-request-creation
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants