payloadcms/payload · warning · APIError

Cannot findDistinct by a virtual field that isn't linked to

Error message

Cannot findDistinct by a virtual field that isn't linked to a relationship field.

What it means

In `findDistinctOperation`, if the resolved field is `virtual` (truthy) but `virtual` is not a string (i.e. `true` or a boolean), it throws `APIError`. Distinct on virtual fields only works when the virtual field declares a relationship path as a string; a bare boolean virtual has no backing relationship to deduplicate against.

Source

Thrown at packages/payload/src/collections/operations/findDistinct.ts:156

      const hasAccess = await fieldResult.field.access.read({
        collection: collectionConfig,
        req,
      })
      if (!hasAccess) {
        throw new Forbidden(req.t)
      }
    }

    await validateSortQuery({
      collectionConfig,
      overrideAccess: overrideAccess!,
      req,
      sort: args.sort,
    })

    if ('virtual' in fieldResult.field && fieldResult.field.virtual) {
      if (typeof fieldResult.field.virtual !== 'string') {
        throw new APIError(
          `Cannot findDistinct by a virtual field that isn't linked to a relationship field.`,
        )
      }

      let relationPath: string = ''
      let currentFields: FlattenedField[] = collectionConfig.flattenedFields
      const fieldPathSegments = fieldResult.field.virtual.split('.')
      for (const segment of fieldResult.field.virtual.split('.')) {
        relationPath = `${relationPath}${segment}`
        fieldPathSegments.shift()
        const field = currentFields.find((e) => e.name === segment)!
        if (
          (field.type === 'relationship' || field.type === 'upload') &&
          typeof field.relationTo === 'string'
        ) {
          break
        }
        if ('flattenedFields' in field) {

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Configure the virtual field with a string relationship path (`virtual: 'status.label'`), or
  2. Choose a non-virtual field for the distinct query.

Example fix

// before
{ name: 'statusLabel', type: 'text', virtual: true }
// after
{ name: 'statusLabel', type: 'text', virtual: 'status.label' }
Defensive patterns

Strategy: validation

Validate before calling

function isDistinctableVirtual(field) {
  return !('virtual' in field) || !field.virtual || typeof field.virtual === 'string'
}

Type guard

function isStringVirtual(field): boolean {
  return typeof field?.virtual === 'string'
}

Prevention

When it happens

Trigger: Calling findDistinct on a field configured with `virtual: true` (boolean) rather than `virtual: 'relation.subField'` (string path).

Common situations: Misconfigured virtual field; attempting distinct on a computed field with no relationship backing.

Related errors


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