diff --git a/docs/admin/views.mdx b/docs/admin/views.mdx
index 3e78a17c9..20035b1c3 100644
--- a/docs/admin/views.mdx
+++ b/docs/admin/views.mdx
@@ -53,7 +53,7 @@ For more granular control, pass a configuration object instead. Payload exposes
| Property | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| **`Component`** \* | Pass in the component that should be rendered when a user navigates to this route. |
+| **`Component`** \* | Pass in the component path that should be rendered when a user navigates to this route. |
| **`path`** \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| **`exact`** | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| **`strict`** | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
@@ -111,7 +111,20 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
components: {
views: {
edit: {
- Component: '/path/to/MyCustomEditView', // highlight-line
+ root: {
+ Component: '/path/to/MyCustomEditView', // highlight-line
+ }
+ // other options include:
+ // default
+ // versions
+ // version
+ // api
+ // livePreview
+ // [key: string]
+ // See "Document Views" for more details
+ },
+ list: {
+ Component: '/path/to/MyCustomListView',
}
},
},
@@ -123,7 +136,7 @@ _For details on how to build Custom Views, see [Building Custom Views](#building
Note:
- The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
+ The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
The following options are available:
@@ -152,18 +165,29 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
admin: {
components: {
views: {
- edit: '/path/to/MyCustomEditView', // highlight-line
+ edit: {
+ root: {
+ Component: '/path/to/MyCustomEditView', // highlight-line
+ }
+ // other options include:
+ // default
+ // versions
+ // version
+ // api
+ // livePreview
+ // [key: string]
+ },
},
},
},
-})
+}
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
Note:
- The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
+ The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
The following options are available:
@@ -199,25 +223,26 @@ export const MyCollectionOrGlobalConfig: SanitizedCollectionConfig = {
},
},
},
-})
+}
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
Note:
- If you need to replace the _entire_ Edit View, including _all_ nested Document Views, use the `Edit` key itself. See [Custom Collection Views](#collection-views) or [Custom Global Views](#global-views) for more information.
+ If you need to replace the _entire_ Edit View, including _all_ nested Document Views, use the `root` key. See [Custom Collection Views](#collection-views) or [Custom Global Views](#global-views) for more information.
The following options are available:
| Property | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| **`default`** | The Default view is the primary view in which your document is edited. |
-| **`versions`** | The Versions view is used to view the version history of a single document. [More details](../versions). |
-| **`version`** | The Version view is used to view a single version of a single document for a given collection. [More details](../versions). |
-| **`api`** | The API view is used to display the REST API JSON response for a given document. |
-| **`livePreview`** | The LivePreview view is used to display the Live Preview interface. [More details](../live-preview). |
+| **`root`** | The Root View overrides all other nested views and routes. No document controls or tabs are rendered when this key is set. |
+| **`default`** | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. |
+| **`versions`** | The Versions View is used to navigate the version history of a single document. It is rendered within the "Versions" tab. [More details](../versions). |
+| **`version`** | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](../versions). |
+| **`api`** | The API View is used to display the REST API JSON response for a given document. It is rendered within the "API" tab. |
+| **`livePreview`** | The LivePreview view is used to display the Live Preview interface. It is rendered within the "Live Preview" tab. [More details](../live-preview). |
### Document Tabs
diff --git a/packages/next/src/views/Document/getViewsFromConfig.tsx b/packages/next/src/views/Document/getViewsFromConfig.tsx
index db49e206b..a84e1b573 100644
--- a/packages/next/src/views/Document/getViewsFromConfig.tsx
+++ b/packages/next/src/views/Document/getViewsFromConfig.tsx
@@ -66,139 +66,89 @@ export const getViewsFromConfig = ({
config?.admin?.livePreview?.globals?.includes(globalConfig?.slug)
if (collectionConfig) {
- const editConfig = collectionConfig?.admin?.components?.views?.edit
- const EditOverride = typeof editConfig === 'function' ? editConfig : null
+ const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
+ routeSegments
- if (EditOverride) {
- CustomView = EditOverride
- }
-
- if (!EditOverride) {
- const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
- routeSegments
-
- if (!docPermissions?.read?.permission) {
- notFound()
- } else {
- // `../:id`, or `../create`
- switch (routeSegments.length) {
- case 3: {
- switch (segment3) {
- case 'create': {
- if ('create' in docPermissions && docPermissions?.create?.permission) {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'default'),
- }
- DefaultView = {
- Component: DefaultEditView,
- }
- } else {
- ErrorView = {
- Component: UnauthorizedView,
- }
- }
- break
- }
-
- default: {
+ if (!docPermissions?.read?.permission) {
+ notFound()
+ } else {
+ // `../:id`, or `../create`
+ switch (routeSegments.length) {
+ case 3: {
+ switch (segment3) {
+ case 'create': {
+ if ('create' in docPermissions && docPermissions?.create?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
}
DefaultView = {
Component: DefaultEditView,
}
- break
+ } else {
+ ErrorView = {
+ Component: UnauthorizedView,
+ }
}
+ break
}
- break
- }
- // `../:id/api`, `../:id/preview`, `../:id/versions`, etc
- case 4: {
- switch (segment4) {
- case 'api': {
- if (collectionConfig?.admin?.hideAPIURL !== true) {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'api'),
- }
- DefaultView = {
- Component: DefaultAPIView,
- }
- }
- break
+ default: {
+ CustomView = {
+ payloadComponent: getCustomViewByKey(views, 'default'),
}
-
- case 'preview': {
- if (livePreviewEnabled) {
- DefaultView = {
- Component: DefaultLivePreviewView,
- }
- }
- break
- }
-
- case 'versions': {
- if (docPermissions?.readVersions?.permission) {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'versions'),
- }
- DefaultView = {
- Component: DefaultVersionsView,
- }
- } else {
- ErrorView = {
- Component: UnauthorizedView,
- }
- }
- break
- }
-
- default: {
- const baseRoute = [
- adminRoute !== '/' && adminRoute,
- 'collections',
- collectionSlug,
- segment3,
- ]
- .filter(Boolean)
- .join('/')
-
- const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
- .filter(Boolean)
- .join('/')
-
- CustomView = {
- payloadComponent: getCustomViewByRoute({
- baseRoute,
- currentRoute,
- views,
- }),
- }
- break
+ DefaultView = {
+ Component: DefaultEditView,
}
+ break
}
- break
}
+ break
+ }
- // `../:id/versions/:version`, etc
- default: {
- if (segment4 === 'versions') {
- if (docPermissions?.readVersions?.permission) {
+ // `../:id/api`, `../:id/preview`, `../:id/versions`, etc
+ case 4: {
+ switch (segment4) {
+ case 'api': {
+ if (collectionConfig?.admin?.hideAPIURL !== true) {
CustomView = {
- payloadComponent: getCustomViewByKey(views, 'version'),
+ payloadComponent: getCustomViewByKey(views, 'api'),
}
DefaultView = {
- Component: DefaultVersionView,
+ Component: DefaultAPIView,
+ }
+ }
+ break
+ }
+
+ case 'preview': {
+ if (livePreviewEnabled) {
+ DefaultView = {
+ Component: DefaultLivePreviewView,
+ }
+ }
+ break
+ }
+
+ case 'versions': {
+ if (docPermissions?.readVersions?.permission) {
+ CustomView = {
+ payloadComponent: getCustomViewByKey(views, 'versions'),
+ }
+ DefaultView = {
+ Component: DefaultVersionsView,
}
} else {
ErrorView = {
Component: UnauthorizedView,
}
}
- } else {
+ break
+ }
+
+ default: {
const baseRoute = [
adminRoute !== '/' && adminRoute,
- collectionEntity,
+ 'collections',
collectionSlug,
segment3,
]
@@ -216,144 +166,176 @@ export const getViewsFromConfig = ({
views,
}),
}
+ break
}
- break
}
+ break
+ }
+
+ // `../:id/versions/:version`, etc
+ default: {
+ if (segment4 === 'versions') {
+ if (docPermissions?.readVersions?.permission) {
+ CustomView = {
+ payloadComponent: getCustomViewByKey(views, 'version'),
+ }
+ DefaultView = {
+ Component: DefaultVersionView,
+ }
+ } else {
+ ErrorView = {
+ Component: UnauthorizedView,
+ }
+ }
+ } else {
+ const baseRoute = [
+ adminRoute !== '/' && adminRoute,
+ collectionEntity,
+ collectionSlug,
+ segment3,
+ ]
+ .filter(Boolean)
+ .join('/')
+
+ const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
+ .filter(Boolean)
+ .join('/')
+
+ CustomView = {
+ payloadComponent: getCustomViewByRoute({
+ baseRoute,
+ currentRoute,
+ views,
+ }),
+ }
+ }
+ break
}
}
}
}
if (globalConfig) {
- const editConfig = globalConfig?.admin?.components?.views?.edit
- const EditOverride = typeof editConfig === 'function' ? editConfig : null
+ const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
- if (EditOverride) {
- CustomView = EditOverride
- }
-
- if (!EditOverride) {
- const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
-
- if (!docPermissions?.read?.permission) {
- notFound()
- } else {
- switch (routeSegments.length) {
- case 2: {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'default'),
- }
- DefaultView = {
- Component: DefaultEditView,
- }
- break
+ if (!docPermissions?.read?.permission) {
+ notFound()
+ } else {
+ switch (routeSegments.length) {
+ case 2: {
+ CustomView = {
+ payloadComponent: getCustomViewByKey(views, 'default'),
}
-
- case 3: {
- // `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
- switch (segment3) {
- case 'api': {
- if (globalConfig?.admin?.hideAPIURL !== true) {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'api'),
- }
- DefaultView = {
- Component: DefaultAPIView,
- }
- }
- break
- }
-
- case 'preview': {
- if (livePreviewEnabled) {
- DefaultView = {
- Component: DefaultLivePreviewView,
- }
- }
- break
- }
-
- case 'versions': {
- if (docPermissions?.readVersions?.permission) {
- CustomView = {
- payloadComponent: getCustomViewByKey(views, 'versions'),
- }
- DefaultView = {
- Component: DefaultVersionsView,
- }
- } else {
- ErrorView = {
- Component: UnauthorizedView,
- }
- }
- break
- }
-
- default: {
- if (docPermissions?.read?.permission) {
- const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
- .filter(Boolean)
- .join('/')
-
- const currentRoute = [baseRoute, segment3, ...remainingSegments]
- .filter(Boolean)
- .join('/')
-
- CustomView = {
- payloadComponent: getCustomViewByRoute({
- baseRoute,
- currentRoute,
- views,
- }),
- }
- DefaultView = {
- Component: DefaultEditView,
- }
- } else {
- ErrorView = {
- Component: UnauthorizedView,
- }
- }
- break
- }
- }
- break
+ DefaultView = {
+ Component: DefaultEditView,
}
+ break
+ }
- default: {
- // `../:slug/versions/:version`, etc
- if (segment3 === 'versions') {
- if (docPermissions?.readVersions?.permission) {
+ case 3: {
+ // `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
+ switch (segment3) {
+ case 'api': {
+ if (globalConfig?.admin?.hideAPIURL !== true) {
CustomView = {
- payloadComponent: getCustomViewByKey(views, 'version'),
+ payloadComponent: getCustomViewByKey(views, 'api'),
}
DefaultView = {
- Component: DefaultVersionView,
+ Component: DefaultAPIView,
+ }
+ }
+ break
+ }
+
+ case 'preview': {
+ if (livePreviewEnabled) {
+ DefaultView = {
+ Component: DefaultLivePreviewView,
+ }
+ }
+ break
+ }
+
+ case 'versions': {
+ if (docPermissions?.readVersions?.permission) {
+ CustomView = {
+ payloadComponent: getCustomViewByKey(views, 'versions'),
+ }
+ DefaultView = {
+ Component: DefaultVersionsView,
}
} else {
ErrorView = {
Component: UnauthorizedView,
}
}
- } else {
- const baseRoute = [adminRoute !== '/' && adminRoute, 'globals', globalSlug]
- .filter(Boolean)
- .join('/')
+ break
+ }
- const currentRoute = [baseRoute, segment3, ...remainingSegments]
- .filter(Boolean)
- .join('/')
+ default: {
+ if (docPermissions?.read?.permission) {
+ const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
+ .filter(Boolean)
+ .join('/')
+ const currentRoute = [baseRoute, segment3, ...remainingSegments]
+ .filter(Boolean)
+ .join('/')
+
+ CustomView = {
+ payloadComponent: getCustomViewByRoute({
+ baseRoute,
+ currentRoute,
+ views,
+ }),
+ }
+ DefaultView = {
+ Component: DefaultEditView,
+ }
+ } else {
+ ErrorView = {
+ Component: UnauthorizedView,
+ }
+ }
+ break
+ }
+ }
+ break
+ }
+
+ default: {
+ // `../:slug/versions/:version`, etc
+ if (segment3 === 'versions') {
+ if (docPermissions?.readVersions?.permission) {
CustomView = {
- payloadComponent: getCustomViewByRoute({
- baseRoute,
- currentRoute,
- views,
- }),
+ payloadComponent: getCustomViewByKey(views, 'version'),
+ }
+ DefaultView = {
+ Component: DefaultVersionView,
+ }
+ } else {
+ ErrorView = {
+ Component: UnauthorizedView,
}
}
- break
+ } else {
+ const baseRoute = [adminRoute !== '/' && adminRoute, 'globals', globalSlug]
+ .filter(Boolean)
+ .join('/')
+
+ const currentRoute = [baseRoute, segment3, ...remainingSegments]
+ .filter(Boolean)
+ .join('/')
+
+ CustomView = {
+ payloadComponent: getCustomViewByRoute({
+ baseRoute,
+ currentRoute,
+ views,
+ }),
+ }
}
+ break
}
}
}
diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx
index 7783c7702..0f49023a1 100644
--- a/packages/next/src/views/Document/index.tsx
+++ b/packages/next/src/views/Document/index.tsx
@@ -60,7 +60,7 @@ export const Document: React.FC = async ({
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
- let ViewOverride: MappedComponent
+ let RootViewOverride: MappedComponent
let CustomView: MappedComponent
let DefaultView: MappedComponent
let ErrorView: MappedComponent
@@ -115,19 +115,18 @@ export const Document: React.FC = async ({
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
- ViewOverride =
- collectionConfig?.admin?.components?.views?.edit?.default &&
- 'Component' in collectionConfig.admin.components.views.edit.default
+ RootViewOverride =
+ collectionConfig?.admin?.components?.views?.edit?.root &&
+ 'Component' in collectionConfig.admin.components.views.edit.root
? createMappedComponent(
- collectionConfig?.admin?.components?.views?.edit?.default
- ?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
+ collectionConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
undefined,
undefined,
- 'collectionConfig?.admin?.components?.views?.edit?.default',
+ 'collectionConfig?.admin?.components?.views?.edit?.root',
)
: null
- if (!ViewOverride) {
+ if (!RootViewOverride) {
const collectionViews = getViewsFromConfig({
collectionConfig,
config,
@@ -157,7 +156,7 @@ export const Document: React.FC = async ({
)
}
- if (!CustomView && !DefaultView && !ViewOverride && !ErrorView) {
+ if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
}
}
@@ -170,9 +169,11 @@ export const Document: React.FC = async ({
const params = new URLSearchParams({
locale: locale?.code,
})
+
if (globalConfig.versions?.drafts) {
params.append('draft', 'true')
}
+
if (locale?.code) {
params.append('locale', locale.code)
}
@@ -181,10 +182,18 @@ export const Document: React.FC = async ({
apiURL = `${serverURL}${apiRoute}/${globalSlug}${apiQueryParams}`
- const editConfig = globalConfig?.admin?.components?.views?.edit
- ViewOverride = typeof editConfig === 'function' ? editConfig : null
+ RootViewOverride =
+ globalConfig?.admin?.components?.views?.edit?.root &&
+ 'Component' in globalConfig.admin.components.views.edit.root
+ ? createMappedComponent(
+ globalConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
+ undefined,
+ undefined,
+ 'globalConfig?.admin?.components?.views?.edit?.root',
+ )
+ : null
- if (!ViewOverride) {
+ if (!RootViewOverride) {
const globalViews = getViewsFromConfig({
config,
docPermissions,
@@ -213,7 +222,7 @@ export const Document: React.FC = async ({
'globalViews?.ErrorView.payloadComponent',
)
- if (!CustomView && !DefaultView && !ViewOverride && !ErrorView) {
+ if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
}
}
@@ -268,7 +277,7 @@ export const Document: React.FC = async ({
initialState={formState}
isEditing={isEditing}
>
- {!ViewOverride && (
+ {!RootViewOverride && (
= async ({
) : (
)}
diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts
index 4b5dc4ca7..24a0cb135 100644
--- a/packages/payload/src/config/types.ts
+++ b/packages/payload/src/config/types.ts
@@ -912,28 +912,44 @@ export type SanitizedConfig = {
'collections' | 'editor' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
>
-export type EditConfig = {
- [key: string]: Partial
- /**
- * Replace or modify individual nested routes, or add new ones:
- * + `default` - `/admin/collections/:collection/:id`
- * + `api` - `/admin/collections/:collection/:id/api`
- * + `livePreview` - `/admin/collections/:collection/:id/preview`
- * + `references` - `/admin/collections/:collection/:id/references`
- * + `relationships` - `/admin/collections/:collection/:id/relationships`
- * + `versions` - `/admin/collections/:collection/:id/versions`
- * + `version` - `/admin/collections/:collection/:id/versions/:version`
- * + `customView` - `/admin/collections/:collection/:id/:path`
- */
- api?: Partial
- default?: Partial
- livePreview?: Partial
- version?: Partial
- versions?: Partial
- // TODO: uncomment these as they are built
- // references?: EditView
- // relationships?: EditView
-}
+export type EditConfig =
+ | {
+ [key: string]: EditViewConfig
+ /**
+ * Replace or modify individual nested routes, or add new ones:
+ * + `default` - `/admin/collections/:collection/:id`
+ * + `api` - `/admin/collections/:collection/:id/api`
+ * + `livePreview` - `/admin/collections/:collection/:id/preview`
+ * + `references` - `/admin/collections/:collection/:id/references`
+ * + `relationships` - `/admin/collections/:collection/:id/relationships`
+ * + `versions` - `/admin/collections/:collection/:id/versions`
+ * + `version` - `/admin/collections/:collection/:id/versions/:version`
+ * + `customView` - `/admin/collections/:collection/:id/:path`
+ *
+ * To override the entire Edit View including all nested views, use the `root` key.
+ */
+ api?: Partial
+ default?: Partial
+ livePreview?: Partial
+ root?: never
+ version?: Partial
+ versions?: Partial
+ // TODO: uncomment these as they are built
+ // references?: EditView
+ // relationships?: EditView
+ }
+ | {
+ api?: never
+ default?: never
+ livePreview?: never
+ /**
+ * Replace or modify _all_ nested document views and routes, including the document header, controls, and tabs. This cannot be used in conjunction with other nested views.
+ * + `root` - `/admin/collections/:collection/:id/**\/*`
+ */
+ root: Partial
+ version?: never
+ versions?: never
+ }
export type EntityDescriptionComponent = CustomComponent
diff --git a/test/admin/collections/CustomViews1.ts b/test/admin/collections/CustomViews1.ts
index d281ae7bb..bc965ee78 100644
--- a/test/admin/collections/CustomViews1.ts
+++ b/test/admin/collections/CustomViews1.ts
@@ -10,7 +10,7 @@ export const CustomViews1: CollectionConfig = {
// This will override the entire Edit View including all nested views, i.e. `/edit/:id/*`
// To override one specific nested view, use the nested view's slug as the key
edit: {
- default: {
+ root: {
Component: '/components/views/CustomEdit/index.js#CustomEditView',
},
},