Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
496d679
feat: add SchemaPath union type generation utility
AlessioGr Feb 26, 2026
74d051d
feat: integrate SchemaPath type into generate:types output
AlessioGr Feb 26, 2026
9ffd086
feat: add schemaPathId field property for stable admin config keys
AlessioGr Feb 26, 2026
2fa155d
feat: add admin config types and builder functions
AlessioGr Feb 26, 2026
36f90de
feat: export admin config types and remove importMap from BasePayload
AlessioGr Feb 26, 2026
7705d61
feat: export admin config builders from payload/shared
AlessioGr Feb 26, 2026
959f1c9
refactor: remove importMap generation system
AlessioGr Feb 26, 2026
e828088
refactor: remove generate:importmap CLI command
AlessioGr Feb 26, 2026
ace967e
refactor: remove importMap from config types and defaults
AlessioGr Feb 26, 2026
3e22e38
refactor: remove importMap from admin types and functions
AlessioGr Feb 26, 2026
282230c
refactor: remove importMap from field/collection/global client creation
AlessioGr Feb 26, 2026
11b8d77
feat: add ClientAdminConfigProvider and RscOverridesProvider contexts
AlessioGr Feb 26, 2026
70bae1e
refactor: wire RscOverridesProvider into RootProvider
AlessioGr Feb 26, 2026
e968c46
feat: RenderField reads from client config and RSC overrides contexts
AlessioGr Feb 26, 2026
4f62389
feat: export admin config providers and hooks from @payloadcms/ui
AlessioGr Feb 26, 2026
196b294
refactor: remove importMap from RenderServerComponent
AlessioGr Feb 26, 2026
95afc8f
refactor: remove importMap from form state rendering
AlessioGr Feb 26, 2026
2e4eaa5
fix: remove isLoading from new array/block rows
AlessioGr Feb 26, 2026
c266811
refactor: replace getFormState server function with no-op
AlessioGr Feb 26, 2026
8f3ab1a
refactor: remove importMap from UI utilities and table state
AlessioGr Feb 26, 2026
afa2724
refactor: remove importMap from all next package views and utilities
AlessioGr Feb 26, 2026
6f3f8e5
refactor: remove importMap from richtext packages
AlessioGr Feb 26, 2026
63a5243
refactor: remove importMap from create-payload-app plugin template
AlessioGr Feb 26, 2026
f168d10
feat: add _community test suite config files for new admin config system
AlessioGr Feb 26, 2026
75ac762
feat: wire admin config files into app layout
AlessioGr Feb 26, 2026
98b4850
refactor: remove importMap from test app layout and init scripts
AlessioGr Feb 26, 2026
32ad6b3
feat: extend admin config with admin/collections/globals/fields secti…
AlessioGr Feb 26, 2026
eec9633
feat: simplify to single payload.config.admin.tsx + optional shared.ts
AlessioGr Feb 26, 2026
c078809
feat: implement 2-file admin config with render-admin-rsc server action
AlessioGr Feb 26, 2026
ddfc248
feat: add server-side admin config cache via React cache()
AlessioGr Feb 26, 2026
35ed2e8
feat: update all admin rendering points to use admin config
AlessioGr Feb 26, 2026
0ad817b
feat: migrate admin test suite to direct imports in admin config files
AlessioGr Feb 26, 2026
3e070a1
fix: move admin-level client components to RSC config
AlessioGr Feb 26, 2026
1835684
fix: update custom view routing and metadata to read from admin config
AlessioGr Feb 26, 2026
aafa9ac
fix: use React.FC references instead of JSX in RSC admin config
AlessioGr Feb 26, 2026
fefddb3
fix: update document tab system to read from admin config
AlessioGr Feb 26, 2026
cdc9ff8
fix: pass field props to admin config Label/Description/Error/BeforeI…
AlessioGr Feb 26, 2026
f3dba89
fix: render Label/Description/Error/BeforeInput/AfterInput even with …
AlessioGr Feb 26, 2026
475eb00
fix: move renderAdminComponent helpers before early returns to avoid …
AlessioGr Feb 26, 2026
d1c9503
fix: call setAdminConfig in handleServerFunctions for client-side nav…
AlessioGr Feb 26, 2026
d5a7a74
fix: use module-level singleton for admin config instead of React cac…
AlessioGr Feb 26, 2026
d3cc663
feat: type-safe admin config keys with SchemaPathMap
AlessioGr Feb 26, 2026
0350321
type fixes
AlessioGr Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions app/(payload)/admin/ClientConfigBridge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'
import type { ClientAdminConfig, SharedAdminConfig } from 'payload'

import { ClientAdminConfigProvider } from '@payloadcms/ui'
import React, { useMemo } from 'react'

import clientConfig from '../../../test/admin/payload.config.admin.client.js'

function mergeSharedIntoClient(
client: ClientAdminConfig,
shared?: SharedAdminConfig,
): ClientAdminConfig {
if (!shared?.fields) {
return client
}

const merged: ClientAdminConfig = { ...client, fields: { ...client.fields } }

for (const [path, sharedField] of Object.entries(shared.fields)) {
if (!merged.fields![path]) {
merged.fields![path] = {}
}
if (sharedField.validate && !merged.fields![path].validate) {
merged.fields![path] = { ...merged.fields![path], validate: sharedField.validate }
}
}

return merged
}

export const ClientConfigBridge: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const mergedConfig = useMemo(() => mergeSharedIntoClient(clientConfig ?? {}), [])

return <ClientAdminConfigProvider config={mergedConfig}>{children}</ClientAdminConfigProvider>
}
10 changes: 5 additions & 5 deletions app/(payload)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
// import '@payloadcms/ui/styles.css' // Uncomment this line if `@payloadcms/ui` in `tsconfig.json` points to `/ui/dist` instead of `/ui/src`
import type { ServerFunctionClient } from 'payload'

import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react'

import { importMap } from './admin/importMap.js'
import rscAdminConfig from '../../test/admin/payload.config.admin.rsc.js'
import { ClientConfigBridge } from './admin/ClientConfigBridge.js'
import './custom.scss'

type Args = {
Expand All @@ -18,14 +18,14 @@ const serverFunction: ServerFunctionClient = async function (args) {
'use server'
return handleServerFunctions({
...args,
adminConfig: rscAdminConfig,
config,
importMap,
})
}

const Layout = ({ children }: Args) => (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
<RootLayout config={config} rscAdminConfig={rscAdminConfig} serverFunction={serverFunction}>
<ClientConfigBridge>{children}</ClientConfigBridge>
</RootLayout>
)

Expand Down
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import './.next/types/routes.d.ts'
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ export const configurePluginProject = ({
const devPayloadConfigPath = path.resolve(projectDirPath, './dev/payload.config.ts')
const devTsConfigPath = path.resolve(projectDirPath, './dev/tsconfig.json')
const indexTsPath = path.resolve(projectDirPath, './src/index.ts')
const devImportMapPath = path.resolve(projectDirPath, './dev/app/(payload)/admin/importMap.js')

const devPayloadConfig = fse.readFileSync(devPayloadConfigPath, 'utf8')
const devTsConfig = fse.readFileSync(devTsConfigPath, 'utf8')
const indexTs = fse.readFileSync(indexTsPath, 'utf-8')
const devImportMap = fse.readFileSync(devImportMapPath, 'utf-8')

const updatedTsConfig = devTsConfig.replaceAll('plugin-package-name-placeholder', projectName)
const updatedImportMap = devImportMap.replaceAll('plugin-package-name-placeholder', projectName)
let updatedIndexTs = indexTs.replaceAll('plugin-package-name-placeholder', projectName)

const pluginExportVariableName = toCamelCase(projectName)
Expand All @@ -46,5 +43,4 @@ export const configurePluginProject = ({
fse.writeFileSync(devPayloadConfigPath, updatedPayloadConfig)
fse.writeFileSync(devTsConfigPath, updatedTsConfig)
fse.writeFileSync(indexTsPath, updatedIndexTs)
fse.writeFileSync(devImportMapPath, updatedImportMap)
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export const DefaultDocumentTab: React.FC<{
{RenderServerComponent({
Component: Pill,
Fallback: Pill_Component,
importMap: req.payload.importMap,
serverProps: {
i18n: req.i18n,
payload: req.payload,
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/elements/DocumentHeader/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export const DocumentTabs: React.FC<{
path: viewPath,
} satisfies DocumentTabClientProps,
Component: tabConfig.Component,
importMap: req.payload.importMap,
key: `tab-${index}`,
serverProps: {
collectionConfig,
Expand Down
11 changes: 10 additions & 1 deletion packages/next/src/elements/DocumentHeader/Tabs/tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DocumentTabConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'

import { getAdminConfig } from '../../../../utilities/adminConfigCache.js'
import { VersionsPill } from './VersionsPill/index.js'

export const documentViewKeys = ['api', 'default', 'livePreview', 'versions']
Expand All @@ -13,7 +14,15 @@ export const getTabs = ({
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
}): { tab: DocumentTabConfig; viewPath: string }[] => {
const adminConfig = getAdminConfig()
const slug = collectionConfig?.slug ?? globalConfig?.slug
const adminEntityViews = slug
? ((collectionConfig ? adminConfig.collections?.[slug] : adminConfig.globals?.[slug]) as any)
?.views?.edit
: undefined

const customViews =
adminEntityViews ||
collectionConfig?.admin?.components?.views?.edit ||
globalConfig?.admin?.components?.views?.edit ||
{}
Expand Down Expand Up @@ -58,7 +67,7 @@ export const getTabs = ({
},
]
.concat(
Object.entries(customViews).reduce((acc, [key, value]) => {
Object.entries(customViews).reduce((acc, [key, value]: [string, any]) => {
if (documentViewKeys.includes(key)) {
return acc
}
Expand Down
16 changes: 5 additions & 11 deletions packages/next/src/elements/Logo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,17 @@ import type React from 'react'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { PayloadLogo } from '@payloadcms/ui/shared'

import { getAdminConfig } from '../../utilities/adminConfigCache.js'

export const Logo: React.FC<ServerProps> = (props) => {
const { i18n, locale, params, payload, permissions, searchParams, user } = props

const {
admin: {
components: {
graphics: { Logo: CustomLogo } = {
Logo: undefined,
},
} = {},
} = {},
} = payload.config
const adminConfig = getAdminConfig()
const CustomLogo = adminConfig.admin?.graphics?.Logo

return RenderServerComponent({
Component: CustomLogo,
Component: CustomLogo as React.ComponentType,
Fallback: PayloadLogo,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand Down
23 changes: 10 additions & 13 deletions packages/next/src/elements/Nav/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
import React from 'react'

import { getAdminConfig } from '../../utilities/adminConfigCache.js'
import { NavHamburger } from './NavHamburger/index.js'
import { NavWrapper } from './NavWrapper/index.js'
import { SettingsMenuButton } from './SettingsMenuButton/index.js'
Expand Down Expand Up @@ -39,13 +40,15 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
return null
}

const {
admin: {
components: { afterNav, afterNavLinks, beforeNav, beforeNavLinks, logout, settingsMenu },
},
collections,
globals,
} = payload.config
const { collections, globals } = payload.config

const adminConfig = getAdminConfig()
const beforeNav = adminConfig.admin?.beforeNav as any
const afterNav = adminConfig.admin?.afterNav as any
const beforeNavLinks = adminConfig.admin?.beforeNavLinks as any
const afterNavLinks = adminConfig.admin?.afterNavLinks as any
const logout = adminConfig.admin?.logout as any
const settingsMenu = adminConfig.admin?.settingsMenu as any

const groups = groupNavItems(
[
Expand Down Expand Up @@ -81,7 +84,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
},
Component: logout?.Button,
Fallback: Logout,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand All @@ -102,7 +104,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
viewType,
},
Component: item,
importMap: payload.importMap,
key: `settings-menu-item-${index}`,
serverProps: {
i18n,
Expand All @@ -123,7 +124,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
viewType,
},
Component: beforeNav,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand All @@ -141,7 +141,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
viewType,
},
Component: beforeNavLinks,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand All @@ -159,7 +158,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
viewType,
},
Component: afterNavLinks,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand All @@ -177,7 +175,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
viewType,
},
Component: afterNav,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
Expand Down
21 changes: 5 additions & 16 deletions packages/next/src/layouts/Root/NestProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import type { Config, ImportMap, ServerProps } from 'payload'
import type { ServerProps } from 'payload'

import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import '@payloadcms/ui/scss/app.scss'
import React from 'react'

type Args = {
readonly children: React.ReactNode
readonly importMap: ImportMap
readonly providers: Config['admin']['components']['providers']
readonly providers: unknown[]
readonly serverProps: ServerProps
}

export function NestProviders({
children,
importMap,
providers,
serverProps,
}: Args): React.ReactNode {
export function NestProviders({ children, providers, serverProps }: Args): React.ReactNode {
return RenderServerComponent({
clientProps: {
children:
providers.length > 1 ? (
<NestProviders
importMap={importMap}
providers={providers.slice(1)}
serverProps={serverProps}
>
<NestProviders providers={providers.slice(1)} serverProps={serverProps}>
{children}
</NestProviders>
) : (
children
),
},
Component: providers[0],
importMap,
Component: providers[0] as React.ComponentType,
serverProps,
})
}
31 changes: 21 additions & 10 deletions packages/next/src/layouts/Root/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ImportMap, LanguageOptions, SanitizedConfig, ServerFunctionClient } from 'payload'
import type {
LanguageOptions,
RscAdminConfig,
SanitizedConfig,
ServerFunctionClient,
} from 'payload'

import { rtlLanguages } from '@payloadcms/translations'
import { ProgressBar, RootProvider } from '@payloadcms/ui'
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
import { cookies as nextCookies } from 'next/headers.js'
import { applyLocaleFiltering } from 'payload/shared'
import { applyLocaleFiltering, getRscSchemaPaths } from 'payload/shared'
import React from 'react'

import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js'
import { getAdminConfig, setAdminConfig } from '../../utilities/adminConfigCache.js'
import { getRequestTheme } from '../../utilities/getRequestTheme.js'
import { initReq } from '../../utilities/initReq.js'
import { checkDependencies } from './checkDependencies.js'
Expand All @@ -25,17 +31,21 @@ export const RootLayout = async ({
children,
config: configPromise,
htmlProps = {},
importMap,
rscAdminConfig,
serverFunction,
}: {
readonly children: React.ReactNode
readonly config: Promise<SanitizedConfig>
readonly htmlProps?: React.HtmlHTMLAttributes<HTMLHtmlElement>
readonly importMap: ImportMap
readonly rscAdminConfig?: RscAdminConfig
readonly serverFunction: ServerFunctionClient
}) => {
checkDependencies()

if (rscAdminConfig) {
setAdminConfig(rscAdminConfig)
}

const {
cookies,
headers,
Expand All @@ -45,7 +55,7 @@ export const RootLayout = async ({
req: {
payload: { config },
},
} = await initReq({ configPromise, importMap, key: 'RootLayout' })
} = await initReq({ configPromise, key: 'RootLayout' })

const theme = getRequestTheme({
config,
Expand Down Expand Up @@ -85,12 +95,14 @@ export const RootLayout = async ({
const clientConfig = getClientConfig({
config,
i18n: req.i18n,
importMap,
user: req.user,
})

await applyLocaleFiltering({ clientConfig, config, req })

const adminConfigProviders = getAdminConfig().admin?.providers
const providers = adminConfigProviders || config.admin?.components?.providers

return (
<html
data-theme={theme}
Expand All @@ -112,18 +124,17 @@ export const RootLayout = async ({
languageOptions={languageOptions}
locale={req.locale}
permissions={req.user ? permissions : null}
rscSchemaPaths={rscAdminConfig ? getRscSchemaPaths(rscAdminConfig) : undefined}
serverFunction={serverFunction}
switchLanguageServerAction={switchLanguageServerAction}
theme={theme}
translations={req.i18n.translations}
user={req.user}
>
<ProgressBar />
{Array.isArray(config.admin?.components?.providers) &&
config.admin?.components?.providers.length > 0 ? (
{Array.isArray(providers) && providers.length > 0 ? (
<NestProviders
importMap={req.payload.importMap}
providers={config.admin?.components?.providers}
providers={providers}
serverProps={{
i18n: req.i18n,
payload: req.payload,
Expand Down
Loading
Loading