payloadcms/payload · error · APIError

Orderable joins must target a single collection

Error message

Orderable joins must target a single collection

What it means

An `orderable: true` join field targets more than one collection (`collection` is an array — a polymorphic join). Ordering rows across collections requires a single target table with a shared order column, so polymorphic orderable joins are rejected.

Source

Thrown at packages/payload/src/fields/config/sanitizeJoinField.ts:63

    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')
  }

  if (Array.isArray(field.collection)) {
    for (const collection of field.collection) {
      const sanitizedField = {
        ...field,
        collection,
      } as FlattenedJoinField

      sanitizeJoinField({
        config,
        field: sanitizedField,
        joinPath,
        joins,
        parentIsLocalized,
        polymorphicJoins,
        validateOnly: true,
      })

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Narrow the join to a single target collection (`collection: 'posts'`) if you need ordering.
  2. If polymorphism is required, set `orderable: false` (or omit it) and accept default ordering.
  3. Split into two separate orderable joins, one per collection.

Example fix

// before
{ type: 'join', name: 'items', orderable: true, collection: ['posts', 'pages'], on: 'owner' }
// after
{ type: 'join', name: 'posts', orderable: true, collection: 'posts', on: 'owner' }
Defensive patterns

Strategy: validation

Validate before calling

function findOrderablePolymorphicJoins(fields) {
  const bad = []
  function walk(list) {
    for (const f of list) {
      if (f?.type === 'join' && f.orderable && Array.isArray(f.collection)) bad.push(f.name)
      if (Array.isArray(f?.fields)) walk(f.fields)
    }
  }
  walk(fields)
  return bad
}

Type guard

function orderableJoinTargetsSingleCollection(f: any): boolean {
  return f?.type !== 'join' || !f.orderable || !Array.isArray(f.collection)
}

Try / catch

try {
  await payload.init({ config })
} catch (err) {
  if (err?.name === 'APIError' && /Orderable joins must target a single/i.test(err?.message ?? '')) {
    console.error('orderable join with multiple collections')
  }
  throw err
}

Prevention

When it happens

Trigger: `{ type: 'join', name: 'x', orderable: true, collection: ['posts', 'pages'], on: 'owner' }`.

Common situations: Wanting a polymorphic join that is also manually reorderable; copy-pasting an orderable join and adding a second collection.

Related errors


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