{"record":{"id":"f0e8a89db20afaaa","repo":"dgraph-io/dgraph","slug":"id-is-not-provided","errorCode":null,"errorMessage":"id is not provided","messagePattern":"id is not provided","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"graphql/resolve/mutation_rewriter.go","lineNumber":1621,"sourceCode":"\t\t// Case 1:\n\t\t// It's an update and we are at top level. So, the UID of node(s) for which\n\t\t// we are rewriting is/are referenced using \"uid(x)\" as part of mutations.\n\t\t// We don't need to create a new blank node in this case.\n\t\t// srcUID is equal to uid(x) in this case.\n\t\t// Case 2:\n\t\t// This is an upsert with Add Mutation and upsertVar is non-empty (which means\n\t\t// the XID at top level exists and this is an upsert).\n\t\t// We continue updating in this case and no new node is created. srcUID will be\n\t\t// equal to uid(variable) in this case. Eg. uid(State1)\n\t\tnewObj[\"uid\"] = srcUID\n\t\tmyUID = srcUID\n\t} else if mutationType == UpdateWithRemove {\n\t\t// It's a remove. As remove can only be part of Update Mutation. It can\n\t\t// be inferred that this is an Update Mutation.\n\t\t// In case of remove of Update, deeper level nodes have to be referenced by ID\n\t\t// or XID. If we have reached this stage, we can be sure that no such reference\n\t\t// to ID or XID exists. In that case, we throw an error.\n\t\terr := errors.Errorf(\"id is not provided\")\n\t\tretErrors = append(retErrors, err)\n\t\treturn nil, upsertVar, retErrors\n\t} else {\n\t\t// We are in Add Mutation or at a deeper level in Update Mutation set.\n\t\t// If we have reached this stage, we can be sure that we need to create a new\n\t\t// node as part of the mutation. The new node is referenced as a blank node like\n\t\t// \"_:Project2\" . myUID will store the variable generated to reference this node.\n\t\tnewObj[\"dgraph.type\"] = dgraphTypes\n\t\tnewObj[\"uid\"] = myUID\n\t\taction = defaultDirectiveAddAct\n\t}\n\n\t// Now we know whether this is a new node or not, we can set @default(add/update) fields\n\tfor _, field := range typ.Fields() {\n\t\tvar pred = field.DgraphPredicate()\n\t\tif newObj[pred] != nil {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":1603,"sourceCodeEnd":1639,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/graphql/resolve/mutation_rewriter.go#L1603-L1639","documentation":"Within an Update mutation's remove path, nested nodes must be identified by an ID (UID) or an @id/XID so Dgraph knows which edges to remove. If a nested object in a remove operation carries neither, Dgraph cannot infer the target and throws this error. Add mutations legitimately create blank nodes, but remove cannot.","triggerScenarios":"Calling an update mutation with a remove input (e.g. updatePost(remove: { author: {} })) where a nested object contains no `id` field and no @id/XID field.","commonSituations":"Client code building remove payloads from partially filled objects, forgetting to include the identifier when only sending the fields to unlink, or schemas where the nested type's XID field was renamed.","solutions":["Include the node's `id` (UID) in each nested object within the remove input.","Alternatively include the @id/XID field value so the node can be resolved by external ID.","If the intent is to clear the whole edge, pass null (or the appropriate scalar) instead of an empty object.","Review the generated mutation payload to ensure the remove branch mirrors the identifier present in the add/set branch."],"exampleFix":"// before\nupdatePost(input: { filter: { id: [\"0x1\"] }, remove: { author: {} } })\n// after\nupdatePost(input: { filter: { id: [\"0x1\"] }, remove: { author: { id: \"0x2\" } } })","handlingStrategy":"validation","validationCode":"function assertRemovable(node) {\n  if (node == null || typeof node !== 'object') return;\n  if (!('id' in node) && node.id == null && !hasXidField(node)) {\n    throw new Error(\"remove input needs an id or @id value\");\n  }\n}\nfunction hasXidField(node) {\n  return Object.entries(node).some(([k, v]) => k !== 'id' && v !== '' && v != null);\n}","typeGuard":"function isIdentifiableNode(n) {\n  return typeof n === 'object' && n !== null && (('id' in n) || ('username' in n));\n}","tryCatchPattern":"try {\n  await client.mutate({ mutation: UPDATE_POST, variables: { input } });\n} catch (e) {\n  if (/id is not provided/.test(e.message)) {\n    // add id or @id value to every nested object in the remove payload\n  } else throw e;\n}","preventionTips":["Always include id or the @id field in every nested object of a remove input.","Use the same identifier shape for set and remove branches of an update.","Build remove payloads from fetched entities so identifiers are present.","Prefer passing null to clear an edge over sending an empty object."],"tags":["graphql","update-mutation","missing-identifier"],"backgroundTag":"missing-node-identifier","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}