{"record":{"id":"a6cd677c956df062","repo":"gatsbyjs/gatsby","slug":"a-plugin-tried-to-update-a-node-field-that-it-does","errorCode":null,"errorMessage":"A plugin tried to update a node field that it doesn't own:\n\nNode id: ${node.id}\nPlugin: ${plugin.name}\nname: ${name}\nvalue: ${value}","messagePattern":"A plugin tried to update a node field that it doesn't own:\n\nNode id: (.+?)\nPlugin: (.+?)\nname: (.+?)\nvalue: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/actions/public.js","lineNumber":936,"sourceCode":"  actionOptions?: ActionOptions\n) => {\n  // Ensure required fields are set.\n  if (!node.internal.fieldOwners) {\n    node.internal.fieldOwners = {}\n  }\n  if (!node.fields) {\n    node.fields = {}\n  }\n\n  // Normalized name of the field that will be used in schema\n  const schemaFieldName = _.includes(name, `___NODE`)\n    ? name.split(`___`)[0]\n    : name\n\n  // Check that this field isn't owned by another plugin.\n  const fieldOwner = node.internal.fieldOwners[schemaFieldName]\n  if (fieldOwner && fieldOwner !== plugin.name) {\n    throw new Error(\n      stripIndent`\n      A plugin tried to update a node field that it doesn't own:\n\n      Node id: ${node.id}\n      Plugin: ${plugin.name}\n      name: ${name}\n      value: ${value}\n      `\n    )\n  }\n\n  // Update node\n  node.fields[name] = value\n  node.internal.fieldOwners[schemaFieldName] = plugin.name\n  node = sanitizeNode(node)\n\n  return {\n    ...actionOptions,","sourceCodeStart":918,"sourceCodeEnd":954,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/actions/public.js#L918-L954","documentation":"createNodeField enforces single-owner semantics per field: the first plugin to set a given field name on a node is recorded in node.internal.fieldOwners[schemaFieldName], and any later createNodeField call for that same schema field from a different plugin throws. This prevents two plugins from silently clobbering each other's extension data on the same node. The schemaFieldName strips any ___NODE suffix so foreign-key fields share ownership with their scalar counterpart.","triggerScenarios":"Calling actions.createNodeField({ node, name, value }) from plugin B when node.internal.fieldOwners[name] (or [name.split('___')[0]]) was already set to plugin A.name, with A.name !== B.name. Fires inside the createNodeField action creator before the ADD_FIELD_TO_NODE action is dispatched.","commonSituations":"Two transformer/source plugins both writing the same field name (e.g. both set `slug` or `excerpt`) on shared node types; a custom plugin reusing a field name that gatsby-transformer-remark or another transformer already owns; copy-pasting field-extension code across plugins.","solutions":["Rename the field your plugin writes so it does not collide (e.g. `myPluginSlug` instead of `slug`).","If you legitimately own the field, ensure only one plugin calls createNodeField for that name; consolidate the logic.","If you are replacing another plugin's behavior, disable/remove that plugin or use a different node type.","Check the error's Plugin + name to see which two plugins conflict, then adjust ownership in config."],"exampleFix":"// before - plugin 'gatsby-source-x' and 'gatsby-source-y' both do:\ncreateNodeField({ node, name: `slug`, value: raw.slug })\n// after - namespace per plugin\ncreateNodeField({ node, name: `xSlug`, value: raw.slug })","handlingStrategy":"validation","validationCode":"// Check ownership before calling createNodeField.\nfunction safeCreateNodeField(actions, { node, name, value, pluginName }) {\n  const schemaName = name.includes('___NODE') ? name.split('___')[0] : name\n  const owner = node.internal.fieldOwners?.[schemaName]\n  if (owner && owner !== pluginName) {\n    // skip or rename instead of throwing\n    actions.createNodeField({ node, name: `${pluginName}_${name}`, value })\n    return\n  }\n  actions.createNodeField({ node, name, value })\n}","typeGuard":"function canOwnField(node, name, pluginName) {\n  const schemaName = name.includes('___NODE') ? name.split('___')[0] : name\n  const owner = node?.internal?.fieldOwners?.[schemaName]\n  return !owner || owner === pluginName\n}","tryCatchPattern":null,"preventionTips":["Namespace field names with your plugin name to avoid collisions.","Coordinate field ownership across plugins in your project config.","Avoid generic field names like 'slug', 'title', 'excerpt' in shared transformers."],"tags":["createnodefield","fields","ownership","plugin-api"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}