payloadcms/payload · error · InvalidConfiguration

Virtual field ${virtualField.name} in ${globalConfig ? `glob

Error message

Virtual field ${virtualField.name} in ${globalConfig ? `global ${globalConfig.slug}` : `collection ${collectionConfig?.slug}`} references 2 or more hasMany relationships on the path ${virtualField.virtual} which is not allowed.

What it means

A virtual field's `virtual` path traverses two or more `hasMany` relationship/upload hops. Payload can resolve a virtual over a single hasMany (returning an array), but chaining two hasMany relationships would produce a fan-out of fan-outs that cannot be projected into one column, so it is rejected as invalid configuration.

Source

Thrown at packages/payload/src/fields/config/sanitize.ts:627

        if (
          foundField.type === 'group' ||
          foundField.type === 'tab' ||
          foundField.type === 'array'
        ) {
          flattenFields = foundField.flattenedFields
        } else if (
          (foundField.type === 'relationship' || foundField.type === 'upload') &&
          idx !== paths.length - 1 &&
          typeof foundField.relationTo === 'string'
        ) {
          if (
            foundField.hasMany &&
            (virtualField.type === 'text' ||
              virtualField.type === 'number' ||
              virtualField.type === 'select')
          ) {
            if (isHasMany) {
              throw new InvalidConfiguration(
                `Virtual field ${virtualField.name} in ${globalConfig ? `global ${globalConfig.slug}` : `collection ${collectionConfig?.slug}`} references 2 or more hasMany relationships on the path ${virtualField.virtual} which is not allowed.`,
              )
            }

            isHasMany = true
            virtualField.hasMany = true
          }
          const relatedCollection = config.collections?.find(
            (e) => e.slug === foundField.relationTo,
          )
          if (relatedCollection) {
            flattenFields = flattenAllFields({ fields: relatedCollection.fields })
          }
        }
      }
    }
  }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Reduce the virtual path so it crosses at most one hasMany relationship.
  2. Restructure so the intermediate hop is a single (belongsTo) relationship or a group/array.
  3. Compute the deeper aggregation in a hook or external query instead of a virtual.

Example fix

// before (both posts and comments are hasMany)
{ name: 'allComments', type: 'text', virtual: 'posts.comments.body' }
// after (single hasMany hop)
{ name: 'postTitles', type: 'text', virtual: 'posts.title' }
Defensive patterns

Strategy: validation

Validate before calling

// approximate static check: count hasMany relationship hops in a virtual path
function countHasManyHops(virtualPath, flatFieldsBySegment) {
  // walk segments, increment when the resolved field is a hasMany relationship/upload
  // reject if hasMany count > 1
  // (requires resolving relationTo->collection fields, similar to the sanitizer)
}

Type guard

function virtualPathHasAtMostOneHasMany(segments, resolve): boolean {
  let hasMany = 0
  for (const seg of segments) {
    const f = resolve(seg)
    if (!f) return false
    if ((f.type === 'relationship' || f.type === 'upload') && f.hasMany) hasMany++
  }
  return hasMany <= 1
}

Try / catch

try {
  await payload.init({ config })
} catch (err) {
  if (err?.name === 'InvalidConfiguration' && /hasMany relationships/i.test(err?.message ?? '')) {
    console.error('Virtual field crosses 2+ hasMany:', err?.message)
  }
  throw err
}

Prevention

When it happens

Trigger: A virtual field on collection A with `virtual: 'posts.comments.body'` where both `posts` (on A) and `comments` (on Post) are hasMany relationships; any path with two hasMany segments.

Common situations: Modeling computed/derived fields that reach across nested collections; trying to flatten deep hierarchies into a virtual.

Related errors


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