Array Row Field
A simple and flexible helper component designed to enhance the readability of Array fields in Payload CMS admin UI by generating meaningful preview labels.

What it does
- Generates preview labels for Array field rows.
- Supports fallback logic between multiple fields.
- Tries to resolve relationship fields and their values.
Install
pnpm dlx shadcn@latest add https://p.livog.com/r/array-row-label.json
Code
'use client'
import { useConfig, useFormFields, useRowLabel } from '@payloadcms/ui'
import { useMemo } from 'react'
import type { ClientComponentProps } from 'payload'
import { useArrayRowDocumentCache } from './use-array-row-document-cache'
const getParentPath = (path: string): string => path.replace(/\.[^.]*$/, '')
type ArrayRowLabelComponentProps = {
fieldToUse: string | string[]
fallbackLabel?: string
prefix?: string
suffix?: string
count?: boolean
prefixCount?: boolean
} & ClientComponentProps
export const ArrayRowLabel = ({ fieldToUse, prefix, suffix, count, prefixCount = false, ...props }: ArrayRowLabelComponentProps) => {
// @ts-ignore
const fallbackLabel = props?.field?.labels?.singular || 'Item'
const { path: rowPath, rowNumber } = useRowLabel<Record<string, unknown>>()
const formFields = useFormFields(([f]) => f)
const { config: { routes: { api: apiRoute } } } = useConfig()
const relativeFieldPaths = useMemo(() => (Array.isArray(fieldToUse) ? fieldToUse : [fieldToUse]), [fieldToUse])
const apiRouteToUse = apiRoute
const relationship = useMemo(() => {
for (const relPath of relativeFieldPaths) {
const parentPath = getParentPath(relPath)
if (parentPath.split('.').length < 2) continue
const maybeRelationship = formFields[`${rowPath}.${parentPath}`]?.value
if (maybeRelationship && typeof maybeRelationship === 'object' && 'relationTo' in maybeRelationship && 'value' in maybeRelationship) {
return {
collection: maybeRelationship.relationTo as string,
id: typeof maybeRelationship.value === 'string' ? maybeRelationship.value : String(maybeRelationship.value),
leafKey: relPath.split('.').pop()!
}
}
}
}, [relativeFieldPaths, formFields, rowPath])
const relatedDoc = useArrayRowDocumentCache(
apiRouteToUse,
relationship?.collection,
relationship?.id,
relationship ? [relationship.leafKey] : []
)
const label = useMemo(() => {
for (const relPath of relativeFieldPaths) {
const direct = formFields[`${rowPath}.${relPath}`]?.value
if (typeof direct === 'string' && direct.trim()) return direct
}
if (relationship && relatedDoc) {
const resolved = (relatedDoc as any)[relationship.leafKey]
if (typeof resolved === 'string' && resolved.trim()) return resolved
}
}, [relativeFieldPaths, formFields, rowPath, relationship, relatedDoc])
const displayLabel = (() => {
const rowIndexLabel = String((rowNumber ?? 0) + 1).padStart(2, '0')
const baseLabel = label ?? fallbackLabel
const shouldShowCount = count === true || (count === undefined && !label)
const counted = !shouldShowCount ? baseLabel : prefixCount ? `${rowIndexLabel} ${baseLabel}` : `${baseLabel} ${rowIndexLabel}`
const withPrefix = prefix ? `${prefix}${counted}` : counted
return suffix ? `${withPrefix}${suffix}` : withPrefix
})()
return (
<span className="row-label" style={{ pointerEvents: 'none' }}>
{displayLabel}
</span>
)
}
Usage
Import the component:
import { customRowLabel } from '@/payload/components/array-row-label';
Basic Usage
Use a single field for labeling:
fields: [
{
name: 'items',
type: 'array',
admin: {
components: {
RowLabel: customRowLabel({ fieldToUse: ['heading'] })
}
},
fields: [ /* your fields here */ ]
}
]
Advanced Usage with Fallback Logic
If the primary field isn't set, fallback to another field or resolve related documents:
fields: [
{
name: 'links',
type: 'array',
admin: {
components: {
RowLabel: customRowLabel({ fieldToUse: ['link.text', 'link.doc.title'] })
}
},
fields: [ /* your fields here */ ]
}
]
In this example, the label logic:
- First attempts to use
link.text
. - Falls back to
link.doc.title
iflink.text
isn't available. - If
link.doc.title
is not set, it will attempt to resolvelink.doc
as a relationship field, and if found then use the.title
from that document (one-level deep only).