payloadcms/payload · error · APIError
Join fields cannot be added to arrays, blocks or globals.
Error message
Join fields cannot be added to arrays, blocks or globals.
What it means
A `join` field was placed where joins are not allowed: inside an array, inside a block, or on a global. The sanitizer detects this because the `joins` argument is `undefined` (it is only passed for top-level collection fields). Join fields need a single owning collection with a DB table, which arrays/blocks/globals cannot provide.
Source
Thrown at packages/payload/src/fields/config/sanitizeJoinField.ts:45
joins,
orderableJoins,
parentIsLocalized,
polymorphicJoins,
validateOnly,
}: {
config: Config
field: FlattenedJoinField | JoinField
joinPath?: string
joins?: SanitizedJoins
/** Tracker for orderable join fields - populated during sanitization */
orderableJoins?: OrderableJoinInfo[]
parentIsLocalized: boolean
polymorphicJoins?: SanitizedJoin[]
validateOnly?: boolean
}) => {
// the `joins` arg is not passed for globals or when recursing on fields that do not allow a join field
if (typeof joins === 'undefined') {
throw new APIError('Join fields cannot be added to arrays, blocks or globals.')
}
if (!field.admin) {
field.admin = {}
}
if (typeof field.maxDepth === 'undefined') {
field.maxDepth = 1
}
const join: SanitizedJoin = {
field,
joinPath: `${joinPath ? joinPath + '.' : ''}${field.name}`,
parentIsLocalized,
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
targetField: undefined,
}
// Orderable joins must target a single collection
if (field.orderable && Array.isArray(field.collection)) {
throw new APIError('Orderable joins must target a single collection')View on GitHub (pinned to 00c58b35c0)
Solutions
- Move the join field to the top level of a collection (not nested in array/block/group/tab-within-array).
- If you need related data inside a nested structure, use a `relationship` field instead of a `join`.
- For globals, model the reverse data as a relationship on a collection instead of a join on the global.
Example fix
// before (join inside array)
{ name: 'items', type: 'array', fields: [
{ name: 'linked', type: 'join', collection: 'posts', on: 'owner' }
]}
// after (join on the collection top level)
fields: [
{ name: 'items', type: 'array', fields: [...] },
{ name: 'linkedPosts', type: 'join', collection: 'posts', on: 'owner' }
] Defensive patterns
Strategy: validation
Validate before calling
function findJoinsInDisallowedScopes(fields, inNested = false) {
const bad = []
for (const f of fields) {
if (f?.type === 'join' && inNested) bad.push(f.name)
if (Array.isArray(f?.fields)) bad.push(...findJoinsInDisallowedScopes(f.fields, true))
if (Array.isArray(f?.blocks)) for (const b of f.blocks) if (b?.fields) bad.push(...findJoinsInDisallowedScopes(b.fields, true))
}
return bad
}
// for globals, ALL joins are disallowed: scan global.fields with inNested=false but treat global context as disallowed Type guard
function joinIsAtCollectionTopLevel(field, context): boolean {
return field?.type !== 'join' || (context === 'collection' && !context.nested)
} Try / catch
try {
await payload.init({ config })
} catch (err) {
if (err?.name === 'APIError' && /Join fields cannot be added/i.test(err?.message ?? '')) {
console.error('join field placed in array/block/global')
}
throw err
} Prevention
- Only declare `join` fields at the top level of a collection config.
- Use `relationship` fields inside arrays/blocks/globals for nested references.
When it happens
Trigger: A `join` field listed in the `fields` of an `array`, in a block's `fields`, or in a global's `fields`.
Common situations: Trying to model bi-directional relationships from inside a nested structure; assuming joins work like relationships everywhere; refactoring a top-level join into a group/array.
Related errors
- Invalid join field ${field.name}. The config does not have a
- Field "${field.name}" is either missing a field type or it d
- Field ${field.label} has reserved name '${fieldName}'.
- Field ${field.label} has invalid name '${fieldName}'. Field
- Field "${field.name}" of type "${field.type}" has an empty r
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/fbd543883fd01307.
Report an issue: GitHub.