Budibase/budibase · error · Error
1:N Relationship Error: Record already linked to another.
Error message
1:N Relationship Error: Record already linked to another.
What it means
Budibase enforces the "one" side of a one-to-many relationship being exclusive: when saving a row, for each linked ID in a 1:N field, the controller looks up existing link documents for that target row (excluding links already pointing back to the row being saved). If any other row is already linked to the target, saving would silently steal the target, so the LinkController throws this error instead. It is a data-integrity guard inside rowSaved, reached via updateLinks during row save.
Source
Thrown at packages/server/src/db/linkedRows/LinkController.ts:241
await getLinkDocuments({
tableId: field.tableId,
rowId: linkId,
})
).filter(
link =>
link.id !== row._id && link.fieldName === linkedSchema.name
)
// check all the related rows exist
const foundRecords = await this._db.getMultiple(
links.map(l => l.id),
{ allowMissing: true, excludeDocs: true }
)
// The 1 side of 1:N is already related to something else
// You must remove the existing relationship
if (foundRecords.length > 0) {
throw new Error(
`1:N Relationship Error: Record already linked to another.`
)
}
}
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
// first check the doc we're linking to exists
try {
await this._db.get(linkId)
} catch (err) {
// skip links that don't exist
continue
}
operations.push(
new LinkDocument(
table._id!,
fieldName,
row._id!,View on GitHub (pinned to a81a902e9a)
Solutions
- Load the child row's current parent first and remove that link (save the old parent row without the child in its link field) before assigning it to the new parent.
- Retry the save in the UI/automation if it was a race, and make the two writers coordinate (e.g. re-fetch the row and apply the assignment once).
- If the relationship should allow many-to-many, change the relationship type on the table so exclusivity is not enforced.
- For bulk imports, de-duplicate child assignments so each child appears under only one parent per batch.
Example fix
// before
destRow.child = [childRow._id]
await saveRow(destRow) // throws: Record already linked to another
// after
const oldParent = await findParentOf(childRow._id)
if (oldParent) {
oldParent.child = oldParent.child.filter(id => id !== childRow._id)
await saveRow(oldParent)
}
destRow.child = [childRow._id]
await saveRow(destRow) Defensive patterns
Strategy: validation
Validate before calling
const links = await getLinkDocuments({ tableId: childTableId, rowId: childRowId })
const otherParent = links.find(l => l.id !== destRowId && l.fieldName === linkFieldName)
if (otherParent) throw new Error("Child already linked to another parent") Try / catch
try {
await saveRow(row)
} catch (err) {
if (err.message.includes("Record already linked to another")) {
// reload row, clear old parent's link, then retry assignment
} else throw err
} Prevention
- Always unlink a child from its current parent before assigning a new parent in 1:N fields
- Serialize concurrent parent-assignment writes to the same child row
- Prefer many-to-many relationships when exclusivity isn't required
When it happens
Trigger: Saving/updating a row where a one-to-many link field is set to a record that is already linked from a different parent row; the request runs LinkController.rowSaved via updateLinks and the getLinkDocuments lookup for the target linkId returns at least one existing link owned by another row.
Common situations: Two users concurrently assign the same child record to different parents; an automation/bulk import assigns a child row that is already linked elsewhere; switching a child's parent without first clearing the old parent's link; restoring or copying rows that carry stale link values.
Related errors
- Relationship Error: Invalid value
- Cannot overwrite existing column.
- DB does not exist
- CouchDB error: ${err.message}
- Source table '${relationship.sourceTable}' not found in data
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/5fa8ed311da4715a.
Report an issue: GitHub.