Skip to main content

Using onCreateNode to combine Drupal bundle schema types in GatsbyJS

Published on

Heads up! After using this, I have found myself needing to run `gatsby clean` before `develop` or `build` when using this approach.

One of the challenges with Drupal is a data model that is split amongst different types of content. This becomes even more true with Drupal Commerce. You have different product types that contain different yet similar schema and need to be displayed in the same buckets.

The Gatsby Node API has an onCreateNode hook. This allows a plugin or theme to react to the creation of a data node. You can add fields to the node or create a new node based on its data! That means we can take nodes of different types and put them into a new node type. Sounds great! 🥳

So I wrote the following code to take all product types and make a singular product schema type.

exports.onCreateNode = ({ node, actions, createNodeId, createContentDigest }) => {
  const { createNode } = actions
  if (node.internal.type.indexOf('commerce_product__') === 0) {
    const newId = createNodeId(node.drupal_id);
    const normalizedTypeNode = {
      id: newId,
      ...node,
      internal: {
        type: 'commerce_product__commerce_product',
      },
    };
    normalizedTypeNode.internal.contentDigest = createContentDigest(normalizedTypeNode)
    createNode(normalizedTypeNode);
  }
}

Right on! It creates a new internal Gatsby UUID so that it doesn't conflict with existing data nodes. Gatsby will fail with an exception that a plugin is trying to update content owned by a different plugin. The createNodeId function generates a custom ID based on the current plugin and source value.

But it didn't work. I kept getting errors 😭! I even open an issue because I thought the root problem was in createNodeId. It wasn't unique enough.

exports.onCreateNode = ({ node, actions, createNodeId, createContentDigest }) => {
  const { createNode } = actions
  if (node.internal.type.indexOf('commerce_product__') === 0) {
    const newId = createNodeId(`normalized_${node.drupal_id}`);
    const normalizedTypeNode = {
      id: newId,
      ...node,
      internal: {
        type: 'commerce_product__commerce_product',
      },
    };
    normalizedTypeNode.internal.contentDigest = createContentDigest(normalizedTypeNode)
    createNode(normalizedTypeNode);
  }
}

Nope. The problem was my use of the spread operator. I was defining the id property before the spread, so it got overwritten 🤦🏼‍♂️.

Changing to the following fixed it all up.

    const normalizedTypeNode = {
      ...node,
      id: createNodeId(node.drupal_id),
      internal: {
        type: 'CommerceProduct',
      },
    };

The Gatsby schema now as a CommerceProduct type which contains all of my product types! The resulting query gave me my clothing and simple product type products.

query MyQuery {
  allCommerceProduct {
    nodes {
      title
    }
  }
}

Now, when building catalog and product display components, I do not need to worry about the root schema type I need to query.