From 1d1240fd13c020499e5ffc6085ad753634832aec Mon Sep 17 00:00:00 2001
From: Patrik <35232443+PatrikKozak@users.noreply.github.com>
Date: Wed, 24 Sep 2025 15:20:54 -0400
Subject: [PATCH] feat: adds admin.formatDocURL function to control list view
linking (#13773)
### What?
Adds a new `formatDocURL` function to collection admin configuration
that allows users to control the linkable state and URLs of first column
fields in list views.
### Why?
To provide a way to disable automatic link creation from the first
column or provide custom URLs based on document data, user permissions,
view context, and document state.
### How?
- Added `formatDocURL` function type to `CollectionAdminOptions` that
receives document data, default URL, request context, collection slug,
and view type
- Modified `renderCell` to call the function when available and handle
three return types:
- `null`: disables linking entirely
- `string`: uses custom URL
- other: falls back to no linking for safety
- Added function to server-only properties to prevent React Server
Components serialization issues
- Updated `DefaultCell` component to support custom `linkURL` prop
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211211792037945
---
docs/configuration/collections.mdx | 71 +++++++++
packages/next/src/views/List/index.tsx | 1 +
packages/payload/src/admin/elements/Cell.ts | 1 +
.../payload/src/collections/config/client.ts | 4 +-
.../payload/src/collections/config/types.ts | 23 ++-
.../src/elements/Table/DefaultCell/index.tsx | 19 ++-
.../TableColumns/buildColumnState/index.tsx | 4 +
.../buildColumnState/renderCell.tsx | 48 +++++-
packages/ui/src/utilities/renderTable.tsx | 5 +
test/admin/collections/FormatDocURL/index.ts | 56 +++++++
test/admin/config.ts | 2 +
test/admin/e2e/list-view/e2e.spec.ts | 145 ++++++++++++++++++
test/admin/payload-types.ts | 31 ++++
test/admin/slugs.ts | 2 +
14 files changed, 402 insertions(+), 10 deletions(-)
create mode 100644 test/admin/collections/FormatDocURL/index.ts
diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx
index fd1b3afe3..0eea5d529 100644
--- a/docs/configuration/collections.mdx
+++ b/docs/configuration/collections.mdx
@@ -136,6 +136,7 @@ The following options are available:
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
+| `formatDocURL` | Function to customize document links in the List View. Return `null` to disable linking, or a string for custom URLs. [More details](#format-document-urls). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
@@ -333,6 +334,76 @@ export const Posts: CollectionConfig = {
}
```
+### Format Document URLs
+
+The `formatDocURL` function allows you to customize how document links are generated in the List View. This is useful for disabling links for certain documents, redirecting to custom destinations, or modifying URLs based on user context or document state.
+
+To define a custom document URL formatter, use the `admin.formatDocURL` property in your Collection Config:
+
+```ts
+import type { CollectionConfig } from 'payload'
+
+export const Posts: CollectionConfig = {
+ // ...
+ admin: {
+ formatDocURL: ({ doc, defaultURL, req, collectionSlug, viewType }) => {
+ // Disable linking for documents with specific status
+ if (doc.status === 'private') {
+ return null
+ }
+
+ // Custom destination for featured posts
+ if (doc.featured) {
+ return '/admin/featured-posts'
+ }
+
+ // Add query parameters based on user role
+ if (req.user?.role === 'admin') {
+ return defaultURL + '?admin=true'
+ }
+
+ // Use default URL for all other cases
+ return defaultURL
+ },
+ },
+}
+```
+
+The `formatDocURL` function receives the following arguments:
+
+| Argument | Description |
+| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `doc` | The document data for the current row |
+| `defaultURL` | The default URL that Payload would normally generate for this document. You can return this as-is, modify it, or replace it entirely. |
+| `req` | The full [PayloadRequest](../types/payload-request) object, providing access to user context, payload instance, and other request data |
+| `collectionSlug` | The slug of the current collection |
+| `viewType` | The current view context (`'list'`, `'trash'`, etc.) where the link is being generated |
+
+The function should return:
+
+- `null` to disable the link entirely (no link will be rendered)
+- A `string` containing the custom URL to use for the link
+- The `defaultURL` parameter to use Payload's default linking behavior
+
+
+ **Tip:** The `defaultURL` parameter saves you from having to reconstruct URLs
+ manually. You can modify it by appending query parameters or use it as a
+ fallback for your custom logic.
+
+
+#### Examples
+
+**Disable linking for certain users:**
+
+```ts
+formatDocURL: ({ defaultURL, req }) => {
+ if (req.user?.role === 'editor') {
+ return null // No link rendered
+ }
+ return defaultURL
+}
+```
+
## GraphQL
You can completely disable GraphQL for this collection by passing `graphQL: false` to your collection config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.
diff --git a/packages/next/src/views/List/index.tsx b/packages/next/src/views/List/index.tsx
index 7062df579..4e98a9c56 100644
--- a/packages/next/src/views/List/index.tsx
+++ b/packages/next/src/views/List/index.tsx
@@ -280,6 +280,7 @@ export const renderListView = async (
orderableFieldName: collectionConfig.orderable === true ? '_order' : undefined,
payload: req.payload,
query,
+ req,
useAsTitle: collectionConfig.admin.useAsTitle,
viewType,
}))
diff --git a/packages/payload/src/admin/elements/Cell.ts b/packages/payload/src/admin/elements/Cell.ts
index 31da1a31f..3b3bac3f5 100644
--- a/packages/payload/src/admin/elements/Cell.ts
+++ b/packages/payload/src/admin/elements/Cell.ts
@@ -77,6 +77,7 @@ export type DefaultCellComponentProps<
customCellProps?: Record
field: TField
link?: boolean
+ linkURL?: string
onClick?: (args: {
cellData: unknown
collectionSlug: SanitizedCollectionConfig['slug']
diff --git a/packages/payload/src/collections/config/client.ts b/packages/payload/src/collections/config/client.ts
index fb2e03fc5..0a10cea67 100644
--- a/packages/payload/src/collections/config/client.ts
+++ b/packages/payload/src/collections/config/client.ts
@@ -29,7 +29,7 @@ export type ServerOnlyCollectionProperties = keyof Pick<
export type ServerOnlyCollectionAdminProperties = keyof Pick<
SanitizedCollectionConfig['admin'],
- 'baseFilter' | 'baseListFilter' | 'components' | 'hidden'
+ 'baseFilter' | 'baseListFilter' | 'components' | 'formatDocURL' | 'hidden'
>
export type ServerOnlyUploadProperties = keyof Pick<
@@ -50,6 +50,7 @@ export type ClientCollectionConfig = {
SanitizedCollectionConfig['admin'],
| 'components'
| 'description'
+ | 'formatDocURL'
| 'joins'
| 'livePreview'
| 'preview'
@@ -97,6 +98,7 @@ const serverOnlyCollectionAdminProperties: Partial
+ req: PayloadRequest
+ /**
+ * The current view context where the link is being generated.
+ * Most relevant values for document linking are 'list' and 'trash'.
+ */
+ viewType?: ViewTypes
+ }) => null | string
/**
* Specify a navigational group for collections in the admin sidebar.
* - Provide a string to place the entity in a custom group.
diff --git a/packages/ui/src/elements/Table/DefaultCell/index.tsx b/packages/ui/src/elements/Table/DefaultCell/index.tsx
index 17490c453..56b40ccd7 100644
--- a/packages/ui/src/elements/Table/DefaultCell/index.tsx
+++ b/packages/ui/src/elements/Table/DefaultCell/index.tsx
@@ -22,6 +22,7 @@ export const DefaultCell: React.FC = (props) => {
field,
field: { admin },
link,
+ linkURL,
onClick: onClickFromProps,
rowData,
viewType,
@@ -62,12 +63,18 @@ export const DefaultCell: React.FC = (props) => {
if (link) {
wrapElementProps.prefetch = false
WrapElement = Link
- wrapElementProps.href = collectionConfig?.slug
- ? formatAdminURL({
- adminRoute,
- path: `/collections/${collectionConfig?.slug}${viewType === 'trash' ? '/trash' : ''}/${encodeURIComponent(rowData.id)}`,
- })
- : ''
+
+ // Use custom linkURL if provided, otherwise use default URL generation
+ if (linkURL) {
+ wrapElementProps.href = linkURL
+ } else {
+ wrapElementProps.href = collectionConfig?.slug
+ ? formatAdminURL({
+ adminRoute,
+ path: `/collections/${collectionConfig?.slug}${viewType === 'trash' ? '/trash' : ''}/${encodeURIComponent(rowData.id)}`,
+ })
+ : ''
+ }
}
if (typeof onClick === 'function') {
diff --git a/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx b/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx
index 710aa691d..51ea0b4b3 100644
--- a/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx
+++ b/packages/ui/src/providers/TableColumns/buildColumnState/index.tsx
@@ -10,6 +10,7 @@ import type {
Field,
PaginatedDocs,
Payload,
+ PayloadRequest,
SanitizedCollectionConfig,
ServerComponentProps,
StaticLabel,
@@ -46,6 +47,7 @@ export type BuildColumnStateArgs = {
enableRowTypes?: boolean
i18n: I18nClient
payload: Payload
+ req?: PayloadRequest
serverFields: Field[]
sortColumnProps?: Partial
useAsTitle: SanitizedCollectionConfig['admin']['useAsTitle']
@@ -79,6 +81,7 @@ export const buildColumnState = (args: BuildColumnStateArgs): Column[] => {
enableRowSelections,
i18n,
payload,
+ req,
serverFields,
sortColumnProps,
useAsTitle,
@@ -249,6 +252,7 @@ export const buildColumnState = (args: BuildColumnStateArgs): Column[] => {
i18n,
isLinkedColumn: enableLinkedCell && colIndex === activeColumnsIndices[0],
payload,
+ req,
rowIndex,
serverField,
viewType,
diff --git a/packages/ui/src/providers/TableColumns/buildColumnState/renderCell.tsx b/packages/ui/src/providers/TableColumns/buildColumnState/renderCell.tsx
index 1c7ec914b..51e9e1f28 100644
--- a/packages/ui/src/providers/TableColumns/buildColumnState/renderCell.tsx
+++ b/packages/ui/src/providers/TableColumns/buildColumnState/renderCell.tsx
@@ -6,10 +6,12 @@ import type {
Document,
Field,
Payload,
+ PayloadRequest,
ViewTypes,
} from 'payload'
import { MissingEditorProp } from 'payload'
+import { formatAdminURL } from 'payload/shared'
import { RenderCustomComponent } from '../../../elements/RenderCustomComponent/index.js'
import { RenderServerComponent } from '../../../elements/RenderServerComponent/index.js'
@@ -31,6 +33,7 @@ type RenderCellArgs = {
readonly i18n: I18nClient
readonly isLinkedColumn: boolean
readonly payload: Payload
+ readonly req?: PayloadRequest
readonly rowIndex: number
readonly serverField: Field
readonly viewType?: ViewTypes
@@ -45,6 +48,7 @@ export function renderCell({
i18n,
isLinkedColumn,
payload,
+ req,
rowIndex,
serverField,
viewType,
@@ -62,10 +66,49 @@ export function renderCell({
('accessor' in clientField ? (clientField.accessor as string) : undefined) ??
('name' in clientField ? clientField.name : undefined)
+ // Check if there's a custom formatDocURL function for this linked column
+ let shouldLink = isLinkedColumn
+ let customLinkURL: string | undefined
+
+ if (isLinkedColumn && req) {
+ const collectionConfig = payload.collections[collectionSlug]?.config
+ const formatDocURL = collectionConfig?.admin?.formatDocURL
+
+ if (typeof formatDocURL === 'function') {
+ // Generate the default URL that would normally be used
+ const adminRoute = req.payload.config.routes?.admin || '/admin'
+ const defaultURL = formatAdminURL({
+ adminRoute,
+ path: `/collections/${collectionSlug}${viewType === 'trash' ? '/trash' : ''}/${encodeURIComponent(String(doc.id))}`,
+ })
+
+ const customURL = formatDocURL({
+ collectionSlug,
+ defaultURL,
+ doc,
+ req,
+ viewType,
+ })
+
+ if (customURL === null) {
+ // formatDocURL returned null = disable linking entirely
+ shouldLink = false
+ } else if (typeof customURL === 'string') {
+ // formatDocURL returned a string = use custom URL
+ shouldLink = true
+ customLinkURL = customURL
+ } else {
+ // formatDocURL returned unexpected type = disable linking for safety
+ shouldLink = false
+ }
+ }
+ }
+
const cellClientProps: DefaultCellComponentProps = {
...baseCellClientProps,
cellData: 'name' in clientField ? findValueFromPath(doc, accessor) : undefined,
- link: isLinkedColumn,
+ link: shouldLink,
+ linkURL: customLinkURL,
rowData: doc,
}
@@ -78,7 +121,8 @@ export function renderCell({
customCellProps: baseCellClientProps.customCellProps,
field: serverField,
i18n,
- link: cellClientProps.link,
+ link: shouldLink,
+ linkURL: customLinkURL,
onClick: baseCellClientProps.onClick,
payload,
rowData: doc,
diff --git a/packages/ui/src/utilities/renderTable.tsx b/packages/ui/src/utilities/renderTable.tsx
index 80e82a983..0c96d83e2 100644
--- a/packages/ui/src/utilities/renderTable.tsx
+++ b/packages/ui/src/utilities/renderTable.tsx
@@ -10,6 +10,7 @@ import type {
ListQuery,
PaginatedDocs,
Payload,
+ PayloadRequest,
SanitizedCollectionConfig,
ViewTypes,
} from 'payload'
@@ -80,6 +81,7 @@ export const renderTable = ({
payload,
query,
renderRowTypes,
+ req,
tableAppearance,
useAsTitle,
viewType,
@@ -102,6 +104,7 @@ export const renderTable = ({
payload: Payload
query?: ListQuery
renderRowTypes?: boolean
+ req?: PayloadRequest
tableAppearance?: 'condensed' | 'default'
useAsTitle: CollectionConfig['admin']['useAsTitle']
viewType?: ViewTypes
@@ -159,6 +162,7 @@ export const renderTable = ({
| 'enableRowSelections'
| 'i18n'
| 'payload'
+ | 'req'
| 'serverFields'
| 'useAsTitle'
| 'viewType'
@@ -170,6 +174,7 @@ export const renderTable = ({
// sortColumnProps,
customCellProps,
payload,
+ req,
serverFields,
useAsTitle,
viewType,
diff --git a/test/admin/collections/FormatDocURL/index.ts b/test/admin/collections/FormatDocURL/index.ts
new file mode 100644
index 000000000..1097ef25d
--- /dev/null
+++ b/test/admin/collections/FormatDocURL/index.ts
@@ -0,0 +1,56 @@
+import type { CollectionConfig } from 'payload'
+
+export const FormatDocURL: CollectionConfig = {
+ slug: 'format-doc-url',
+ admin: {
+ // Custom formatDocURL function to control linking behavior
+ formatDocURL: ({ doc, defaultURL, req, collectionSlug, viewType }) => {
+ // Disable linking for documents with title 'no-link'
+ if (doc.title === 'no-link') {
+ return null
+ }
+
+ // Custom link for documents with title 'custom-link'
+ if (doc.title === 'custom-link') {
+ return '/custom-destination'
+ }
+
+ // Example: Add query params based on user email (fallback for normal cases)
+ if (
+ req.user?.email === 'dev@payloadcms.com' &&
+ viewType !== 'trash' &&
+ doc._status === 'draft'
+ ) {
+ return defaultURL + '?admin=true'
+ }
+
+ // Example: Different behavior in trash view (check this before user-specific logic)
+ if (viewType === 'trash') {
+ return defaultURL + '?from=trash'
+ }
+
+ // Example: Collection-specific behavior for published docs
+ if (collectionSlug === 'format-doc-url' && doc._status === 'published') {
+ return defaultURL + '?published=true'
+ }
+
+ // For all other documents, just return the default URL
+ return defaultURL
+ },
+ },
+ trash: true,
+ versions: {
+ drafts: true,
+ },
+ fields: [
+ {
+ name: 'title',
+ type: 'text',
+ required: true,
+ },
+ {
+ name: 'description',
+ type: 'textarea',
+ },
+ ],
+}
diff --git a/test/admin/config.ts b/test/admin/config.ts
index 9d161712b..dbaf45e55 100644
--- a/test/admin/config.ts
+++ b/test/admin/config.ts
@@ -12,6 +12,7 @@ import { DisableBulkEdit } from './collections/DisableBulkEdit.js'
import { DisableCopyToLocale } from './collections/DisableCopyToLocale.js'
import { DisableDuplicate } from './collections/DisableDuplicate.js'
import { EditMenuItems } from './collections/editMenuItems.js'
+import { FormatDocURL } from './collections/FormatDocURL/index.js'
import { Geo } from './collections/Geo.js'
import { CollectionGroup1A } from './collections/Group1A.js'
import { CollectionGroup1B } from './collections/Group1B.js'
@@ -183,6 +184,7 @@ export default buildConfigWithDefaults({
DisableDuplicate,
DisableCopyToLocale,
EditMenuItems,
+ FormatDocURL,
BaseListFilter,
with300Documents,
ListDrawer,
diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts
index dc10271f3..0cca5820a 100644
--- a/test/admin/e2e/list-view/e2e.spec.ts
+++ b/test/admin/e2e/list-view/e2e.spec.ts
@@ -19,6 +19,7 @@ import { customAdminRoutes } from '../../shared.js'
import {
arrayCollectionSlug,
customViews1CollectionSlug,
+ formatDocURLCollectionSlug,
geoCollectionSlug,
listDrawerSlug,
placeholderCollectionSlug,
@@ -75,6 +76,7 @@ describe('List View', () => {
let withListViewUrl: AdminUrlUtil
let placeholderUrl: AdminUrlUtil
let disableBulkEditUrl: AdminUrlUtil
+ let formatDocURLUrl: AdminUrlUtil
let user: any
let virtualsUrl: AdminUrlUtil
let noTimestampsUrl: AdminUrlUtil
@@ -102,6 +104,7 @@ describe('List View', () => {
withListViewUrl = new AdminUrlUtil(serverURL, listDrawerSlug)
placeholderUrl = new AdminUrlUtil(serverURL, placeholderCollectionSlug)
disableBulkEditUrl = new AdminUrlUtil(serverURL, 'disable-bulk-edit')
+ formatDocURLUrl = new AdminUrlUtil(serverURL, formatDocURLCollectionSlug)
virtualsUrl = new AdminUrlUtil(serverURL, virtualsSlug)
noTimestampsUrl = new AdminUrlUtil(serverURL, noTimestampsSlug)
const context = await browser.newContext()
@@ -1912,6 +1915,148 @@ describe('List View', () => {
await expect(drawer.locator('.table > table > tbody > tr')).toHaveCount(2)
})
+
+ describe('formatDocURL', () => {
+ beforeEach(async () => {
+ // Clean up any existing formatDocURL documents
+ await payload.delete({
+ collection: formatDocURLCollectionSlug,
+ where: { id: { exists: true } },
+ })
+ })
+
+ test('should disable linking for documents with title "no-link"', async () => {
+ // Create test documents
+ await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: { title: 'no-link', description: 'This should not be linkable' },
+ })
+
+ const normalDoc = await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: { title: 'normal', description: 'This should be linkable normally' },
+ })
+
+ await page.goto(formatDocURLUrl.list)
+ await expect(page.locator(tableRowLocator)).toHaveCount(2)
+
+ // Find the row with "no-link" title - it should NOT have a link
+ const noLinkRow = page.locator(tableRowLocator).filter({ hasText: 'no-link' })
+ const noLinkTitleCell = noLinkRow.locator('td').nth(1)
+ await expect(noLinkTitleCell.locator('a')).toHaveCount(0)
+
+ // Find the row with "normal" title - it should have a link with admin=true query param
+ // (because we're logged in as dev@payloadcms.com)
+ const normalRow = page.locator(tableRowLocator).filter({ hasText: 'normal' })
+ const normalTitleCell = normalRow.locator('td').nth(1)
+ const normalLink = normalTitleCell.locator('a')
+ await expect(normalLink).toHaveCount(1)
+ await expect(normalLink).toHaveAttribute(
+ 'href',
+ `${adminRoutes.routes?.admin}/collections/${formatDocURLCollectionSlug}/${normalDoc.id}?admin=true`,
+ )
+ })
+
+ test('should use custom destination for documents with title "custom-link"', async () => {
+ await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: { title: 'custom-link', description: 'This should link to custom destination' },
+ })
+
+ await page.goto(formatDocURLUrl.list)
+ await expect(page.locator(tableRowLocator)).toHaveCount(1)
+
+ // Find the row with "custom-link" title - it should link to custom destination
+ const customLinkRow = page.locator(tableRowLocator).filter({ hasText: 'custom-link' })
+ const customLinkTitleCell = customLinkRow.locator('td').nth(1)
+ const customLink = customLinkTitleCell.locator('a')
+ await expect(customLink).toHaveCount(1)
+ await expect(customLink).toHaveAttribute('href', '/custom-destination')
+ })
+
+ test('should add admin query param for dev@payloadcms.com user', async () => {
+ // This test verifies the user-based URL modification
+ const adminDoc = await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: { title: 'admin-test', description: 'This should have admin query param' },
+ })
+
+ await page.goto(formatDocURLUrl.list)
+ await expect(page.locator(tableRowLocator)).toHaveCount(1)
+
+ // Since we're logged in as dev@payloadcms.com, links should have ?admin=true
+ const adminRow = page.locator(tableRowLocator).filter({ hasText: 'admin-test' })
+ const adminTitleCell = adminRow.locator('td').nth(1)
+ const adminLink = adminTitleCell.locator('a')
+ await expect(adminLink).toHaveCount(1)
+ await expect(adminLink).toHaveAttribute(
+ 'href',
+ new RegExp(
+ `${adminRoutes.routes?.admin}/collections/${formatDocURLCollectionSlug}/${adminDoc.id}\\?admin=true`,
+ ),
+ )
+ })
+
+ test('should use different URL for trash view', async () => {
+ // Create a document and then move it to trash
+ const trashDoc = await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: { title: 'trash-test', description: 'This should show trash URL' },
+ })
+
+ // Move the document to trash by setting deletedAt (not delete)
+ await payload.update({
+ collection: formatDocURLCollectionSlug,
+ id: trashDoc.id,
+ data: {
+ deletedAt: new Date().toISOString(),
+ },
+ })
+
+ // Go to trash view
+ await page.goto(`${formatDocURLUrl.list}/trash`)
+ await expect(page.locator(tableRowLocator)).toHaveCount(1)
+
+ // In trash view, the formatDocURL should add ?from=trash
+ const trashRow = page.locator(tableRowLocator).filter({ hasText: 'trash-test' })
+ const trashTitleCell = trashRow.locator('td').nth(1)
+ const trashLink = trashTitleCell.locator('a')
+ await expect(trashLink).toHaveCount(1)
+ await expect(trashLink).toHaveAttribute(
+ 'href',
+ new RegExp(
+ `${adminRoutes.routes?.admin}/collections/${formatDocURLCollectionSlug}/trash/${trashDoc.id}\\?from=trash`,
+ ),
+ )
+ })
+
+ test('should add published query param for published documents', async () => {
+ // Create a published document
+ const publishedDoc = await payload.create({
+ collection: formatDocURLCollectionSlug,
+ data: {
+ title: 'published-test',
+ description: 'This is a published document',
+ _status: 'published',
+ },
+ })
+
+ await page.goto(formatDocURLUrl.list)
+ await expect(page.locator(tableRowLocator)).toHaveCount(1)
+
+ // Published documents should have ?published=true added
+ const publishedRow = page.locator(tableRowLocator).filter({ hasText: 'published-test' })
+ const publishedTitleCell = publishedRow.locator('td').nth(1)
+ const publishedLink = publishedTitleCell.locator('a')
+ await expect(publishedLink).toHaveCount(1)
+ await expect(publishedLink).toHaveAttribute(
+ 'href',
+ new RegExp(
+ `${adminRoutes.routes?.admin}/collections/${formatDocURLCollectionSlug}/${publishedDoc.id}\\?published=true`,
+ ),
+ )
+ })
+ })
})
async function createPost(overrides?: Partial): Promise {
diff --git a/test/admin/payload-types.ts b/test/admin/payload-types.ts
index 9b7cefab1..ef6d77364 100644
--- a/test/admin/payload-types.ts
+++ b/test/admin/payload-types.ts
@@ -87,6 +87,7 @@ export interface Config {
'disable-duplicate': DisableDuplicate;
'disable-copy-to-locale': DisableCopyToLocale;
'edit-menu-items': EditMenuItem;
+ 'format-doc-url': FormatDocUrl;
'base-list-filters': BaseListFilter;
with300documents: With300Document;
'with-list-drawer': WithListDrawer;
@@ -123,6 +124,7 @@ export interface Config {
'disable-duplicate': DisableDuplicateSelect | DisableDuplicateSelect;
'disable-copy-to-locale': DisableCopyToLocaleSelect | DisableCopyToLocaleSelect;
'edit-menu-items': EditMenuItemsSelect | EditMenuItemsSelect;
+ 'format-doc-url': FormatDocUrlSelect | FormatDocUrlSelect;
'base-list-filters': BaseListFiltersSelect | BaseListFiltersSelect;
with300documents: With300DocumentsSelect | With300DocumentsSelect;
'with-list-drawer': WithListDrawerSelect | WithListDrawerSelect;
@@ -514,6 +516,19 @@ export interface EditMenuItem {
updatedAt: string;
createdAt: string;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "format-doc-url".
+ */
+export interface FormatDocUrl {
+ id: string;
+ title: string;
+ description?: string | null;
+ updatedAt: string;
+ createdAt: string;
+ deletedAt?: string | null;
+ _status?: ('draft' | 'published') | null;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "base-list-filters".
@@ -709,6 +724,10 @@ export interface PayloadLockedDocument {
relationTo: 'edit-menu-items';
value: string | EditMenuItem;
} | null)
+ | ({
+ relationTo: 'format-doc-url';
+ value: string | FormatDocUrl;
+ } | null)
| ({
relationTo: 'base-list-filters';
value: string | BaseListFilter;
@@ -1096,6 +1115,18 @@ export interface EditMenuItemsSelect {
updatedAt?: T;
createdAt?: T;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "format-doc-url_select".
+ */
+export interface FormatDocUrlSelect {
+ title?: T;
+ description?: T;
+ updatedAt?: T;
+ createdAt?: T;
+ deletedAt?: T;
+ _status?: T;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "base-list-filters_select".
diff --git a/test/admin/slugs.ts b/test/admin/slugs.ts
index 9126ecabb..67cdb59e3 100644
--- a/test/admin/slugs.ts
+++ b/test/admin/slugs.ts
@@ -24,6 +24,7 @@ export const customFieldsSlug = 'custom-fields'
export const listDrawerSlug = 'with-list-drawer'
export const virtualsSlug = 'virtuals'
+export const formatDocURLCollectionSlug = 'format-doc-url'
export const collectionSlugs = [
usersCollectionSlug,
customViews1CollectionSlug,
@@ -41,6 +42,7 @@ export const collectionSlugs = [
disableDuplicateSlug,
listDrawerSlug,
virtualsSlug,
+ formatDocURLCollectionSlug,
]
export const customGlobalViews1GlobalSlug = 'custom-global-views-one'