{"record":{"id":"3068b21e67113033","repo":"gatsbyjs/gatsby","slug":"the-plugin-pluginname-deleted-a-node-of-a-typ","errorCode":null,"errorMessage":"The plugin \"${pluginName}\" deleted a node of a type owned by another plugin.\n\nThe node type \"${internalNode.internal.type}\" is owned by \"${previouslyRecordedOwnerName}\".\n\nThe node object passed to \"deleteNode\":\n\n${JSON.stringify(internalNode, null, 4)}\n\nThe plugin deleting the node:\n\n${JSON.stringify(plugin, null, 4)}","messagePattern":"The plugin \"(.+?)\" deleted a node of a type owned by another plugin\\.\n\nThe node type \"(.+?)\" is owned by \"(.+?)\"\\.\n\nThe node object passed to \"deleteNode\":\n\n(.+?)\n\nThe plugin deleting the node:\n\n(.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/reducers/type-owners.ts","lineNumber":86,"sourceCode":"  action: ActionsUnion\n): IGatsbyState[\"typeOwners\"] => {\n  switch (action.type) {\n    case `DELETE_NODE`: {\n      const { plugin, payload: internalNode } = action\n\n      if (plugin && internalNode && !action.isRecursiveChildrenDelete) {\n        const pluginName = plugin.name\n\n        const previouslyRecordedOwnerName = typeOwners.typesToPlugins.get(\n          internalNode.internal.type\n        )\n\n        if (\n          internalNode &&\n          previouslyRecordedOwnerName &&\n          previouslyRecordedOwnerName !== pluginName\n        ) {\n          throw new Error(stripIndent`\n            The plugin \"${pluginName}\" deleted a node of a type owned by another plugin.\n\n            The node type \"${\n              internalNode.internal.type\n            }\" is owned by \"${previouslyRecordedOwnerName}\".\n\n            The node object passed to \"deleteNode\":\n\n            ${JSON.stringify(internalNode, null, 4)}\n\n            The plugin deleting the node:\n\n            ${JSON.stringify(plugin, null, 4)}\n        `)\n        }\n      }\n\n      return typeOwners","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/reducers/type-owners.ts#L68-L104","documentation":"Symmetric to node creation, the type-owners reducer guards node deletion: if a plugin tries to deleteNode a node whose internal.type is owned by a different plugin, it throws. Children of a deleted node are exempt (action.isRecursiveChildrenDelete skips the check) so owners can cascade-delete descendants, but a foreign plugin cannot delete another plugin's typed nodes directly.","triggerScenarios":"Plugin B calls deleteNode on a node whose internal.internal.type is owned by plugin A (and isRecursiveChildrenDelete is false). The reducer looks up typesToPlugins.get(type) and compares to the deleting plugin's name.","commonSituations":"A plugin trying to clean up nodes it did not create (e.g. a sync plugin deleting stale CMS nodes); incorrect parent/child wiring causing a plugin to attempt direct deletion of foreign nodes; copy-pasted delete logic from another plugin.","solutions":["Only delete nodes your plugin owns; for relationships, use createParentChildLink and let the owner manage lifecycle.","If you genuinely need to remove a foreign node, have the owning plugin expose an API or signal to delete it.","Verify you are not dispatching deleteNode in a loop over getNodes() that includes other plugins' nodes."],"exampleFix":"// before\ngetNodes().filter(n => n.internal.type === `SourceXPost`).forEach(deleteNode) // run by wrong plugin\n// after - let the owning plugin handle its own deletions, or filter to your own type\ngetNodes().filter(n => n.internal.type === `MyType`).forEach(n => actions.deleteNode({ node: n }))","handlingStrategy":"validation","validationCode":"// Only delete nodes of types your plugin owns.\nfunction deleteOwnedOnly(actions, state, pluginName, node) {\n  const owner = state.typeOwners?.typesToPlugins?.get(node.internal.type)\n  if (owner && owner !== pluginName) {\n    throw new Error(`Refusing to delete foreign node type ${node.internal.type} (owned by ${owner})`)\n  }\n  actions.deleteNode({ node })\n}","typeGuard":"function ownsNodeType(node, pluginName, state) {\n  const owner = state?.typeOwners?.typesToPlugins?.get(node?.internal?.type)\n  return !owner || owner === pluginName\n}","tryCatchPattern":null,"preventionTips":["Filter getNodes() results to your own types before deleting.","Let the owning plugin manage lifecycle of its nodes.","Use createParentChildLink rather than manually deleting foreign nodes."],"tags":["type-owners","deletenode","ownership","plugin-api"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}