remix-run/remix · error · Error

hasManyThrough expects a through relation whose source table

Error message

hasManyThrough expects a through relation whose source table matches {getTableName(source)}

What it means

hasManyThrough builds a many relation through an intermediate relation; the through relation must itself originate from the same source table. This error fires when throughRelation.sourceTable does not match the table you called hasManyThrough on, which would make the join chain ambiguous.

Source

Thrown at packages/data-table/src/lib/table.ts:937

  })
}

/**
 * Defines a one-to-many relation from `source` to `target` through an intermediate relation.
 * @param source Source table.
 * @param target Target table.
 * @param relationOptions Through relation configuration.
 * @returns A relation descriptor.
 */
export function hasManyThrough<source extends AnyTable, target extends AnyTable>(
  source: source,
  target: target,
  relationOptions: HasManyThroughOptions<source, target>,
): Relation<source, target, 'many'> {
  let throughRelation = relationOptions.through

  if (throughRelation.sourceTable !== source) {
    throw new Error(
      'hasManyThrough expects a through relation whose source table matches ' +
        getTableName(source),
    )
  }

  let throughTargetKey = normalizeKeysForTable(
    throughRelation.targetTable,
    relationOptions.throughTargetKey,
    'throughTargetKey',
    getTablePrimaryKey(throughRelation.targetTable),
  )
  let throughForeignKey = normalizeKeySelector(
    target,
    relationOptions.throughForeignKey,
    'throughForeignKey',
    [inferForeignKey(getTableName(throughRelation.targetTable))],
  )

View on GitHub (pinned to 9696913134)

Solutions

  1. Pass a through relation created from the same source table as the first argument
  2. Double-check the argument order: hasManyThrough(source, target, { through })
  3. Define the through relation inline with belongsTo(source, ...) so the source table always matches

Example fix

// before
const accountTasks = (table: Tables) =>
  hasManyThrough(accounts, tasks, { through: relations.user /* defined on tasks */ })
// after
const accountTasks = (table: Tables) =>
  hasManyThrough(accounts, tasks, { through: accountUsers /* belongsTo(accounts, users) */ })
Defensive patterns

Strategy: validation

Validate before calling

if (throughRelation.sourceTable !== source) throw new Error('through relation source mismatch')

Prevention

When it happens

Trigger: Calling hasManyThrough(source, target, { through: someRelation }) where someRelation was created with belongsTo/hasMany on a different source table than the one passed as source.

Common situations: Reusing a relation defined on another table (e.g. passing tasks.user when defining an accounts relation), or reordering arguments so source is actually the target.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/a5ef573e030caadb. Report an issue: GitHub.