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

  1. Correct the `collection` slug to exactly match a registered collection's `slug`.
  2. Ensure the target collection is declared in `buildConfig({ collections: [...] })`.
  3. 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

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


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/68fed116eb5fe367. Report an issue: GitHub.