payloadcms/payload · error · InvalidFieldJoin
Invalid join field ${field.name}. The config does not have a
Error message
Invalid join field ${field.name}. The config does not have a field '${field.on}' in collection '${field.collection}'. What it means
A join field's `collection` slug does not match any collection registered in the config. The sanitizer looks up `config.collections` for the slug and throws `InvalidFieldJoin` when nothing matches, because the join's target table does not exist.
Source
Thrown at packages/payload/src/fields/config/sanitizeJoinField.ts:95
joins,
parentIsLocalized,
polymorphicJoins,
validateOnly: true,
})
}
if (Array.isArray(polymorphicJoins)) {
polymorphicJoins.push(join)
}
return
}
const joinCollection = config.collections?.find(
(collection) => collection.slug === field.collection,
)
if (!joinCollection) {
throw new InvalidFieldJoin(field)
}
const relationshipField = getFieldByPath({
fields: flattenAllFields({ cache: true, fields: joinCollection.fields }),
path: field.on,
})
if (
!relationshipField ||
(relationshipField.field.type !== 'relationship' && relationshipField.field.type !== 'upload')
) {
throw new InvalidFieldJoin(join.field)
}
if (relationshipField.pathHasLocalized) {
join.getForeignPath = ({ locale }) => {
return relationshipField.localizedPath.replace('<locale>', locale!)
}View on GitHub (pinned to 00c58b35c0)
Solutions
- Correct the `collection` slug to exactly match a registered collection's `slug`.
- Ensure the target collection is declared in `buildConfig({ collections: [...] })`.
- Watch for slug casing — Payload slugs are typically lowercase plural by convention.
Example fix
// before (no collection named 'Post')
{ type: 'join', name: 'linked', collection: 'Post', on: 'owner' }
// after
{ type: 'join', name: 'linked', collection: 'posts', on: 'owner' } Defensive patterns
Strategy: validation
Validate before calling
function assertJoinCollectionsExist(config) {
const slugs = new Set(config.collections.map(c => c.slug))
const bad = []
function walk(fields) {
for (const f of fields) {
if (f?.type === 'join') {
const targets = Array.isArray(f.collection) ? f.collection : [f.collection]
for (const t of targets) if (!slugs.has(t)) bad.push(`${f.name} -> ${t}`)
}
if (Array.isArray(f?.fields)) walk(f.fields)
}
}
for (const c of config.collections) walk(c.fields)
return bad
} Type guard
function joinCollectionExists(f: any, knownSlugs: Set<string>): boolean {
if (f?.type !== 'join') return true
const targets = Array.isArray(f.collection) ? f.collection : [f.collection]
return targets.every((t: string) => knownSlugs.has(t))
} Try / catch
try {
await payload.init({ config })
} catch (err) {
if (err?.name === 'InvalidFieldJoin') console.error('join collection not found:', err?.data)
throw err
} Prevention
- Reference collection slugs via shared constants to prevent typos and case mismatch.
- After renaming a collection, grep for `collection:` usages in join fields.
When it happens
Trigger: `{ type: 'join', collection: 'Post', on: 'owner' }` when the collection slug is actually `posts`; referencing a collection before it is defined; typo in the slug.
Common situations: Renaming a collection without updating join references; copy-pasting across projects with different slugs; case mismatch (`Post` vs `posts`).
Related errors
- Field "${field.name}" of type "${field.type}" has an empty r
- Virtual field ${virtualField.name} in ${globalConfig ? `glob
- Join fields cannot be added to arrays, blocks or globals.
- Relationship field was not found
- Field "${field.name}" is either missing a field type or it d
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/68fed116eb5fe367.
Report an issue: GitHub.