prisma/prisma ยท error
PSL_JUNCTION_TARGET_FK_NOT_ID
PSL_JUNCTION_TARGET_FK_NOT_ID
Error message
Backrelation list field "${candidate.modelName}.${candidate.field.name}" found junction model "${nearMiss.junctionModelName}", but its foreign key to "${candidate.targetModelName}" does not reference "${candidate.targetModelName}"'s @id. The junction's target-side foreign key must reference "${candidate.targetModelName}"'s full @id columns for many-to-many recognition. What it means
Raised during many-to-many junction recognition for a backrelation list field. The resolver found a candidate junction model linking both sides, but that junction's foreign key to the TARGET model does not reference the target model's full @id columns (childColumnsInTargetIdOrder returned undefined because the FK's referenced columns don't line up 1:1 with the target's id columns). For an N:M junction, the target-side FK must point at exactly the target's identity columns.
Source
Thrown at packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts:387
}
}
return { pairs, nearMisses };
}
function junctionNearMissDiagnostic(
candidate: ModelBackrelationCandidate,
nearMiss: JunctionNearMiss,
sourceId: string,
): ContractSourceDiagnostic {
const listField = `${candidate.modelName}.${candidate.field.name}`;
const data = {
listField,
junctionModel: nearMiss.junctionModelName,
targetModel: candidate.targetModelName,
};
if (nearMiss.reason === 'target-fk-not-id') {
return {
code: 'PSL_JUNCTION_TARGET_FK_NOT_ID',
message: `Backrelation list field "${listField}" found junction model "${nearMiss.junctionModelName}", but its foreign key to "${candidate.targetModelName}" does not reference "${candidate.targetModelName}"'s @id. The junction's target-side foreign key must reference "${candidate.targetModelName}"'s full @id columns for many-to-many recognition.`,
sourceId,
span: candidate.field.span,
data,
};
}
return {
code: 'PSL_JUNCTION_ID_NOT_FK_COVERING',
message: `Backrelation list field "${listField}" found junction-shaped model "${nearMiss.junctionModelName}" linking "${candidate.modelName}" and "${candidate.targetModelName}", but its id does not cover exactly its foreign-key columns. Declare @@id([...]) on "${nearMiss.junctionModelName}" listing exactly the two foreign-key columns for many-to-many recognition.`,
sourceId,
span: candidate.field.span,
data,
};
}
function manyToManyRelationNode(
candidate: ModelBackrelationCandidate,
pair: JunctionFkPair,View on GitHub (pinned to 1243cdffbe)
Solutions
- Change the junction's target-side `@relation(references: [...])` to list exactly the target model's full @id columns.
- If the target has a composite @@id, make the junction FK reference every column of that @@id.
- Alternatively, switch the target's identity so the column the junction already references becomes the @id.
- If a junction was not intended, rename or remove the backrelation list field so it isn't recognized as a many-to-many side.
Example fix
// before
model User { id Int @id }
model Group { id Int @id }
model Membership {
user User @relation(fields: [userId], references: [id])
group Group @relation(fields: [groupId], references: [code]) // code is unique, not @id
userId Int
groupId Int
@@unique([userId, groupId])
}
// after
model Membership {
user User @relation(fields: [userId], references: [id])
group Group @relation(fields: [groupId], references: [id])
userId Int
groupId Int
@@unique([userId, groupId])
} Defensive patterns
Strategy: validation
Validate before calling
// For an implicit m:n, verify the junction's target-side FK references the target's full @id.
function findJunctionTargetFkNotId(models) {
const idCols = new Map(models.map(m => [m.name, m.fields.filter(f => f.isId).map(f => f.name)]));
const offenders = [];
for (const junction of models) {
for (const fk of junction.foreignKeys ?? []) {
const targetId = idCols.get(fk.targetModelName) ?? [];
const sameSet = (a, b) => a.length === b.length && a.every(c => b.includes(c));
if (!sameSet(fk.references, targetId)) {
offenders.push(`${junction.name} -> ${fk.targetModelName}`);
}
}
}
return offenders;
} Type guard
// True when a junction FK references exactly the target's id columns.
const fkCoversTargetId = (fk, targetIdCols) => {
const sameSet = (a, b) => a.length === b.length && a.every(c => b.includes(c));
return sameSet(fk.references, targetIdCols);
}; Try / catch
const fatal = diagnostics.filter( d => d.code === 'PSL_JUNCTION_TARGET_FK_NOT_ID' ); if (fatal.length) throw new SchemaValidationError(fatal);
Prevention
- Make the junction's target-side @relation point at the full primary key of the target model.
- For composite-PK targets, list every id column in `references`.
- Avoid pointing junction FKs at a unique-but-not-id column; m:n recognition requires the @id.
- When renaming a target's @id column, update all junction FK references in the same change.
When it happens
Trigger: Emitted at psl-relation-resolution.ts:385-392 via junctionNearMissDiagnostic when findJunctionFkPairs records a near-miss with reason `target-fk-not-id` (psl-relation-resolution.ts:362-365). Triggered when a junction model has FKs to both the parent and target models, but the target-side FK references a non-id column (e.g. a unique column or a subset of a composite id).
Common situations: Junction whose target-side FK points at a `@unique` column instead of the @id; target model has a composite @@id and the junction FK only references part of it; refactoring a target's primary key without updating the junction's references; pointing the junction at a natural key that is unique but not the declared id.
Related errors
- PSL_JUNCTION_ID_NOT_FK_COVERING
- PSL_INVALID_ATTRIBUTE_SYNTAX
- PSL_INVALID_RELATION_ATTRIBUTE
- PSL_AMBIGUOUS_BACKRELATION
- PSL_EXTENSION_UNKNOWN_PARAMETER
AI-assisted analysis of prisma/prisma@1243cdffbe (2026-08-01).
Data as JSON: /data/errors/9601f0f52415ad62.json.
Report an issue: GitHub.