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
- Pass a through relation created from the same source table as the first argument
- Double-check the argument order: hasManyThrough(source, target, { through })
- 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
- Define through relations inline from the source table
- Keep relation factories per-table so sources cannot be mixed up
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
- Relation key mismatch between "{sourceTableName}" ({sourceKe
- create({ returnRow: true }) failed to load inserted row
- Relation "' + relationName + '" is not defined for source ta
- hasManyThrough relation is missing through metadata
- Unknown transaction token: + token.id
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/a5ef573e030caadb.
Report an issue: GitHub.