Budibase/budibase · error · Error
Relationship Error: Invalid value
Error message
Relationship Error: Invalid value
What it means
`rowSaved` processes link fields after a row save. For each link-type field, the row's value must be an array of link docs (each containing at least an _id). If the field value is non-null but not an array — e.g. a single id string, an object, or a number — the controller cannot compute link diffs and throws 'Relationship Error: Invalid value'.
Source
Thrown at packages/server/src/db/linkedRows/LinkController.ts:184
* When a row is saved this will carry out the necessary operations to make sure
* the link has been created/updated.
* @returns returns the row that has been cleaned and prepared to be written to the DB - links
* have also been created.
*/
async rowSaved() {
const table = await this.table()
const row = this._row!
const operations = []
// get link docs to compare against
const linkDocs = (await this.getRowLinkDocs(row._id!)) as LinkDocument[]
for (let fieldName of Object.keys(table.schema)) {
// get the links this row wants to make
const rowField = row[fieldName]
const field = table.schema[fieldName]
if (field.type === FieldType.LINK && rowField != null) {
// Expects an array of docs with at least their _id
if (!Array.isArray(rowField)) {
throw new Error("Relationship Error: Invalid value")
}
// check which links actual pertain to the update in this row
const thisFieldLinkDocs = linkDocs.filter(
linkDoc =>
linkDoc.doc1.fieldName === fieldName ||
linkDoc.doc2.fieldName === fieldName
)
const linkDocIds = thisFieldLinkDocs.map(linkDoc => {
return linkDoc.doc1.rowId === row._id
? linkDoc.doc2.rowId
: linkDoc.doc1.rowId
})
// if 1:N, ensure that this ID is not already attached to another record
const linkedTable = await this._db.get<Table>(field.tableId)
const linkedSchema = linkedTable.schema[field.fieldName]
View on GitHub (pinned to a81a902e9a)
Solutions
- Wrap the value in an array of ids: field: ["id1", "id2"] instead of field: "id1".
- In automations, add a step (JS/script) that normalizes the binding to an array before the row update.
- In the builder, bind the link field to the multi-select's array of values, not its raw string.
Example fix
// before
await api.post(`/tables/${tableId}/rows`, { ...row, related: "row_456" })
// after
await api.post(`/tables/${tableId}/rows`, { ...row, related: ["row_456"] }) Defensive patterns
Strategy: type-guard
Validate before calling
const isLinkIds = (v: unknown): boolean =>
v == null || (Array.isArray(v) && v.every(x => typeof x === "string"))
if (!isLinkIds(row.related)) throw new Error("link fields must be arrays of row ids (or null)") Type guard
const isLinkIdArray = (v: unknown): v is string[] => Array.isArray(v) && v.every((x): x is string => typeof x === "string")
Try / catch
try {
await api.post(`/tables/${tableId}/rows`, row)
} catch (err) {
if (err.message.includes("Relationship Error: Invalid value")) {
for (const [k, v] of Object.entries(row)) {
if (linkFields.has(k) && v != null && !Array.isArray(v)) row[k] = [v]
}
return api.post(`/tables/${tableId}/rows`, row)
}
throw err
} Prevention
- Always send link fields as arrays of row ids.
- Normalize upstream bindings (query results, form values) to arrays before row updates.
- In automations, insert a script step to coerce scalars into arrays for link fields.
- Bind multi-select components' value arrays, not their string representations.
When it happens
Trigger: Saving/updating a row where a link field is set to "row_id_123" (single string) instead of ["row_id_123"], or to an object like {"_id":"x"}, via the REST /rows endpoint, an automation Create/Update Row step, or the builder UI binding a scalar into a multi-select link field.
Common situations: API clients forgetting link fields are arrays; automations passing a query-result string straight into a link field; form components bound to the wrong type producing a single value instead of an array of selected row ids.
Related errors
- Column names can't contain special characters
- Source table '${relationship.sourceTable}' not found in data
- Target table '${relationship.targetTable}' not found in data
- Cannot re-use the linked column name for a linked table.
- 1:N Relationship Error: Record already linked to another.
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/1edc49c7836b8076.
Report an issue: GitHub.