{"record":{"id":"94afa36b81d3ed42","repo":"gatsbyjs/gatsby","slug":"nodes-can-only-be-updated-by-their-owner-node","errorCode":null,"errorMessage":"Nodes can only be updated by their owner. Node \"${node.id}\" is owned by \"${oldNode.internal.owner}\" and another plugin \"${owner}\" tried to update it.","messagePattern":"Nodes can only be updated by their owner\\. Node \"(.+?)\" is owned by \"(.+?)\" and another plugin \"(.+?)\" tried to update it\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/reducers/type-owners.ts","lineNumber":115,"sourceCode":"            The plugin deleting the node:\n\n            ${JSON.stringify(plugin, null, 4)}\n        `)\n        }\n      }\n\n      return typeOwners\n    }\n    case `CREATE_NODE`: {\n      const { plugin, oldNode, payload: node } = action\n      const { owner, type } = node.internal\n\n      setTypeOwner(type, plugin, typeOwners, node)\n\n      // If the node has been created in the past, check that\n      // the current plugin is the same as the previous.\n      if (oldNode && oldNode.internal.owner !== owner) {\n        throw new Error(\n          stripIndent`\n            Nodes can only be updated by their owner. Node \"${node.id}\" is\n            owned by \"${oldNode.internal.owner}\" and another plugin \"${owner}\"\n            tried to update it.\n          `\n        )\n      }\n\n      return typeOwners\n    }\n\n    default:\n      return typeOwners\n  }\n}\n","sourceCodeStart":97,"sourceCodeEnd":131,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/reducers/type-owners.ts#L97-L131","documentation":"When createNode is called for an id that already exists (oldNode present), the type-owners reducer checks that the new node's internal.owner matches the existing node's internal.owner. If they differ it throws, because a node id is an identity contract and allowing a second plugin to 'recreate' (overwrite) a node owned by another plugin would silently hijack data. This complements per-type ownership with per-node ownership continuity.","triggerScenarios":"Plugin B calls createNode with an id that already exists in state and was created by plugin A (oldNode.internal.owner !== B's owner). Reached when two plugins generate colliding ids, or the same id is reused by different plugins across rebuilds.","commonSituations":"ID collision: two plugins generating the same id prefix/scheme (e.g. both using `path-1`); switching the plugin that owns a node type without clearing cache; a plugin change that altered internal.owner derivation while old nodes still persist.","solutions":["Namespace your node ids so they cannot collide with other plugins (e.g. `source-x:${rawId}`).","Clear .cache after changing which plugin owns a type so stale oldNode entries are gone.","Ensure the same plugin consistently creates the same node id across builds."],"exampleFix":"// before\ncreateNode({ id: raw.slug, internal: { type: `Post`, ... } }) // collides across plugins\n// after\ncreateNode({ id: `my-source-${raw.slug}`, internal: { type: `Post`, ... } })","handlingStrategy":"validation","validationCode":"// Namespace node ids so they cannot collide across plugins.\nfunction namespacedId(pluginName, rawId) {\n  return `${pluginName}::${rawId}`\n}\n// createNode({ id: namespacedId('my-source', raw.id), ... })","typeGuard":"function isStableNamespacedId(id, pluginName) {\n  return typeof id === 'string' && id.startsWith(`${pluginName}::`)\n}","tryCatchPattern":null,"preventionTips":["Derive ids from a plugin prefix + content digest.","Run gatsby clean after changing which plugin owns a type.","Keep id-generation deterministic across builds."],"tags":["type-owners","createnode","ownership","node-id","plugin-api"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}