medusajs/medusa · error · MedusaError
Many-to-one relation ${relation.name} must be set with an ID
Error message
Many-to-one relation ${relation.name} must be set with an ID What it means
Thrown while building the write payload in mikro-orm-repository.ts: a many-to-one relation was supplied as a nested object without an `id` property (and no other ID source), and Medusa does not support creating the related entity inline during this upsert. Many-to-one relations must be set by referencing an existing ID.
Source
Thrown at packages/core/utils/src/dal/mikro-orm/mikro-orm-repository.ts:1243
// If it is a many-to-one we ensure the ID is set for when we want to set/unset an association
if (relation.kind === ReferenceKind.MANY_TO_ONE) {
if (originalData === null) {
entryCopy[relation.joinColumns[0]] = null
return null
}
// The relation can either be a primitive or the entity object, depending on how it's defined on the model
let relationId
if (isString(originalData)) {
relationId = originalData
} else if ("id" in originalData) {
relationId = originalData.id
}
// We don't support creating many-to-one relations, so we want to throw if someone doesn't pass the ID
if (!relationId) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Many-to-one relation ${relation.name} must be set with an ID`
)
}
entryCopy[relation.joinColumns[0]] = relationId
return originalData
}
return undefined
}
// Returns a POJO object with the ID populated from the entity model hooks
protected getEntityWithId(
manager: SqlEntityManager,
entityName: string,
data: any
): Record<string, any> & { id: string } {View on GitHub (pinned to 5e06e544a2)
Solutions
- Change the nested object to reference an existing entity: { relation: { id: 'existing_id' } } or set the FK field directly (relation_id).
- If the related entity does not exist yet, create it first with its own service call, then pass its returned ID in the upsert.
- If the field is actually a one-to-many/many-to-many managed relation, restructure the payload so the parent owns the array of children rather than the reverse.
Example fix
// before
await pricingService.upsert([
{ price_set: { name: 'default' } }, // many-to-one without id
])
// after
const set = await pricingService.createPriceSets([{ name: 'default' }])
await pricingService.upsert([
{ price_set: { id: set[0].id } }, // set with an ID
]) Defensive patterns
Strategy: validation
Validate before calling
for (const item of payload) {
for (const [key, value] of Object.entries(item)) {
if (value && typeof value === 'object' && !('id' in value) && isManyToOne(key)) {
throw new Error(`Relation ${key} must reference an existing id`)
}
}
} Type guard
function hasId(v: unknown): v is { id: string } {
return typeof v === 'object' && v !== null && typeof (v as any).id === 'string' && (v as any).id.length > 0
} Prevention
- Create related entities first, then reference them by id in upsert payloads.
- Sanitize API input so nested objects without ids are rejected with a clear 400.
- Keep FK helper fields (e.g. customer_id) as the canonical way to set many-to-one links.
When it happens
Trigger: Upserting with payload { variant: { title: 'x', options: {...} } } where `variant` is many-to-one and the object carries no id; passing a partially-built object (e.g. from a form) instead of { id: 'variant_123' }; passing an object whose id field is undefined after destructuring.
Common situations: Admin/API payloads that embed full nested objects instead of IDs; assuming create-or-connect semantics (upsertWithReplace does not create many-to-one targets); data transformations that strip or rename the id key; migrating from a flow that previously created the child entity separately.
Related errors
- Nonexistent relations were passed during upsert: ${nonexiste
- invalid_data
- Cannot set value ${value} for ${columnName}.
- 42703
- 23503
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/69268d4107deaa28.
Report an issue: GitHub.