Product Option Value ID Changes when Variants Are Edited

We have an app that allows our users to “better group” customers based on what they purchase. More specifically things like “Red version of some shirt”. One thing the customers requested was the ability to keep tracking the same “item” when product and options are renamed. So, at most fine grained level, what we need to track are orders with a specific Option Value (mind you, not a variant because customers want Red shirts, not XL-Red shirts).

After implementing this logic, I was thrown off when I realized the ProductOptionValue Id completely changes when you edit the option value name. This was completely unexpected as an Id is supposed to be that one stronghold that people can rely on to uniquely identify things as the extrinsic properties change freely.

It seems very much inefficient to capture the product updates and then update the potentially tens of thousands of entries in our database for each option value as well as the customer segments and other side entities created through our system linked to this option.

Is there any way for me to track orders with a specific option value (i.e. Red) even when the product/value/variant names might change?

Would also welcome some insight on why this particular Id changes along with the name. Genuinely curious why this decision was made

02:23:29 │                     remix │ [
02:23:29 │                     remix │   {
02:23:29 │                     remix │     name: 'Date',
02:23:29 │                     remix │     optionValue: {
02:23:29 │                     remix │       id: 'gid://shopify/ProductOptionValue/5711658451218',
02:23:29 │                     remix │       name: 'HOB2'
02:23:29 │                     remix │     },
02:23:29 │                     remix │     value: 'HOB2'
02:23:29 │                     remix │   }
02:23:29 │                     remix │ ]

After changing HOB2 → HOB3

02:27:39 │                     remix │ [
02:27:39 │                     remix │   {
02:27:39 │                     remix │     name: 'Date',
02:27:39 │                     remix │     optionValue: {
02:27:39 │                     remix │       id: 'gid://shopify/ProductOptionValue/5711975612690',
02:27:39 │                     remix │       name: 'HOB3'
02:27:39 │                     remix │     },
02:27:39 │                     remix │     value: 'HOB3'
02:27:39 │                     remix │   }
02:27:39 │                     remix │ ]

The query that the data is extracted from (printing out selectedOptions

`#graphql
    query ($id: ID!){
        productVariant(id: $id) {
              id
              title
              displayName
              selectedOptions{
                 name
                 optionValue {
                     id
                     name
                  }
                  value
               }
               title
         }
     } 
`

Hey @Sinan_Pehlivanoglu,

Thanks for the post and the clarity on how the changing ID is making things difficult and inneficient.

I’m happy to dig in to this further with you. It would help to have a little more context on how the products are being updated (specific mutations and responses if possible). That will help me set up to see this on my own test store and see if there are any alternatives that will help keep the data you need (or at least how to access it when an option changes).

Thank you @KyleG-Shopify for your response and your offer for help. The option name is changed through the admin dashboard, our app doesn’t mutate any products.

The minimum steps to reproduce would be the following:

  1. Create a product with options (one option with two values would be sufficient). Name one of the option values “My Option v1”
  2. Fetch the product through the GraphQL API, make sure options { .. optionValues{id name} ..} is included in the response. Locate the Product Option Value ID for My Option v1 and make note of it
  3. Navigate to admin dashboard, click on the product you created, scroll down to the variants. Click on “My Option v1”, edit the name to “My Option v2”
  4. Once again fetch the product through the GraphQL API. Locate the option value ID for “My Option v2” and make note of it. You will notice the ids no longer match even though a new value wasn’t created, but an existing one was edited

I am including a screen recording of this for your convenience which you can access:

https://drive.google.com/file/d/1yh02QmmgIG11gFwKXq8lniEvzD5x6Hd8/view?usp=sharing

Thank you in advance for your help. Please let me know if there is any further information I can provide

@KyleG-Shopify Hi Kyle, I was wondering if you had time to check this issue out. I still haven’t found an alternative and we really this functionality for our customers

Thanks for following up @Sinan_Pehlivanoglu. I apologize for the delay. I left for holiday shortly after this and missed responding on my return.

I investigated this and found that there were recent changes in how the admin updates products.

For context, update mutations like productVariantsBulkUpdate preserve orphaned ProductOptionValue IDs for data integrity (as mentioned in the docs), but sync mutations like productSet don’t preserve these values by design.

I’d like to pass on feedback to our team to better highlight how this is affecting you. I’m very curious to know more context about your tracking system that relies on old option values and how it’s set up? Have you considered other approaches like webhooks to keep your data in sync?

@KyleG-Shopify no worries at all. I hope you had a nice vacation!

While this is valuable context, as I mentioned, the updates are done through the admin dashboard, as that is how our customers/stores would be updating the variant names. Therefore, we are stuck with whatever mutation the Shopify admin dashboard is using under the hood. We don’t really have a say in it.

We create Shopify Customer Segments for people purchase a specific option value of a product. Segments are automatically updated as new orders come in. Imagine a shirt that comes in sizes S, M, L and colors Red and Blue. We track all orders for Red shirts, so in reality track orders for 3 variants S-Red, M-Red, L-Red. This is just a small, toy example. In reality the number of variants to be tracked can be much larger that it way it is best to track by something that uniquely identities the desired option value. To reiterate, imagine the following scenario:

  1. Store creates a shirt product with Sizes S, M, L and colors “Rd” and “Blue”. Let’s say this product has product id p1
  2. They instruct our app to track all orders for “Rd” shirts, no matter the size
  3. Orders come in, the customer segment for p1-Rd gets updated
  4. After a while, the merchant realize they made a typo, and that the option value should be “Red” and not “Rd”
  5. Now our app needs a way to send future orders for p1-Red to the segment for p1-Rd. And the only way to do this is, if the app can somehow conclude that these are in fact the same option values but the name has been updated.

Without a unique identifier, there is an entire decidability concern. Multiple option values could have been updated in one update so I can’t do process of elimination. A few option value could have been added and the old one could have been deleted which means they are not the same option etc.

**Re: Webhooks **
I am not entirely sure how this would help. Perhaps you could elaborate? Looking at the product/update webhook (Webhooks), it seems like it just returns me the new option names? What extra information does this get me that refetching the product when needed doesn’t? The webhook returns some information about the variants, but again, I am not concerned with the variants, I am concerned with the options.

Thanks for all that context around your customer segmentation system and the challenges with tracking option values across name changes. I’ll definitely pass on to our products team as feedback on how the ID instability is impacting real use cases.

I suggested webhooks because the products/update webhook would let you detect when option values change and proactively bridge your segments. When the webhook fires after “Rd” becomes “Red”, you could compare the previous product state with the new one, identify that position 1 of the Color option changed, then immediately add “Red” to any existing segment filters for “Rd”. This keeps your customer segments intact while the underlying ProductOptionValue IDs change behind the scenes.

I’m also thinking of another potential workaround. Standardized product taxonomy with metafield-linked options could provide more stable references, though it would require merchants to migrate from traditional options.

Hey @Sinan_Pehlivanoglu! Following up here as I did some further testing.

I ran through the exact reproduction steps you outlined (including the steps from your video) and I’m getting different results. When I edit an option value name in the admin dashboard (like changing “pinkblue” to “pink” or even the option name itself from “Color” to “Colors”), the ProductOption ID stays the same. The ID only changes when I delete an option value entirely and create a new one, which makes sense since that’s actually creating a new resource.

Just to help me understand what you’re seeing, are you able to grab the x-request-id from your browser console on that mutation? I’m curious if there might be something different about your product setup or if there’s a specific workflow in the admin that’s triggering the delete/recreate behavior instead of the in-place update. If you’re still seeing the ID change with a simple name edit, I’d love to dig into this further to understand why our results are different.

Hi @KyleG-Shopify thank you for looking into this. Based on your findings, I noticed a very weird inconsistency. I will highlight this below. Before I go any further, just to confirm are you looking at ProductOptionId or ProductOptionValueId ? The relevant id is ProductOptionValueId. ProductOption objects holds all the possible values and therefore is not of use.

**The Inconsistency: **

There are two ways to update a variant option name in the admin console

  1. Through the main product page (/store/:name/products/:pid)

  1. Through the variant page (/store/:name/products/:pid/variants/:vid)

And it seems like, these two pages behave differently. If you edit the option name through the product page (as in #1), then the ProductOptionValueId stays the same. However, if it is updated through the variant page (as in #2), then the ProductOptionValueId changes. I think this is what you were referring earlier with different mutation behaviors. See the video recording of both:

In short, mutation going to https://admin.shopify.com/api/shopify/kronos-test?operation=ProductSaveOptionUpdate&type=mutation with requestID 756bbae0-0532-423b-90f8-afdd39d3289a-1753902487 seems to preserve the value id. Whereas requests going to either https://admin.shopify.com/api/shopify/kronos-test?operation=ProductVariantProductOptionUpdate&type=mutation or https://admin.shopify.com/api/shopify/kronos-test?operation=ProductVariantUpdate&type=mutation, with respective request IDs b8a08ab2-2760-49f3-bfd3-938a5fdf6461-1753902624 and bacc504f-278e-423e-a5c9-0d00b70b3338-1753902625, at least one of these result in the value ID being mutated. Which is somewhat of a confusing behavior. I feel like the admin dashboard should have uniform behavior as much as possible.

I will talk to my team. While this would be bad development, maybe we can communicate to our customers that they should only use the product page for updates.

If they don’t agree, I am curious about the metafield path. What would the merchants need to change to allow us to use metafields? Can’t we just link metadata to options freely?

Hey @Sinan_Pehlivanoglu,

I did some further digging and I can confirm what you are seeing is expected behavior. The reason you’re seeing the difference when editing the option on the variant page compared to the product page is because when you’re editing the option on a variant, you’re editing it for only that individual variant; whereas when you edit on the product page, it’s updating all option values for that option.

Since you’re changing the option value for only a single variant, a new id needs to be created for that specific option.

I recorded a quick video for you here to demonstrate this: