Line item property applied to all items

Hey team, I’m writing an app which adds properties to line items in the cart to be used later in the workflow, but I’m running into an issue when the property I want to add is hidden from the customer (using the “_“ prefix).

When adding a normal property using the “cart.bulkCartUpdate” (cart.bulkCartUpdate) and also splitting a line item it works fine, I see both items in the cart and only one of them contains the new property. But, when I do this with a hidden property (with the property name starting with “_“ like: “_hiddenProperty“, I only see one item in the cart (expected, as the property is hidden), but the property gets applied to all of the line items quantity instead of only one. Below I’ll put a simple modal.tsx so you can test the behavior too!

import React from "react";
import {
  Screen,
  ScrollView,
  Text,
  reactExtension,
  useApi,
  Button,
  useCartSubscription,
  Stack,
  Section,
  TextField,
} from "@shopify/ui-extensions-react/point-of-sale";

function generateUUID() { // Public Domain/MIT
    var d = new Date().getTime();//Timestamp
    var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16;//random number between 0 and 16
        if(d > 0){//Use timestamp until depleted
            r = (d + r)%16 | 0;
            d = Math.floor(d/16);
        } else {//Use microseconds since page-load if supported
            r = (d2 + r)%16 | 0;
            d2 = Math.floor(d2/16);
        }
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}

const SmartGridModal = () => {
  const api = useApi<"pos.home.modal.render">();
  const cart = useCartSubscription();
  const [propertyValues, setPropertyValues] = React.useState<{
    [key: string]: string;
  }>({});

  const handlePropertyInputChange = (uuid: string, value: string) => {
    setPropertyValues({ ...propertyValues, [uuid]: value });
  };

  const handleAddProduct = async (variantId: number | undefined, withHiddenProperty: boolean) => {
    if (!variantId) {
      console.error("No variant ID available for this line item");
      return;
    }

    try {
      // Add the same variant to the cart
      api.toast.show(`Added property to one unit of ${variantId}`);

      const lineItemsUpdated = cart.lineItems.reduce((acc, item: any) => {
        if (item.variantId === variantId) {
          if (withHiddenProperty) {
            acc.push(
              {
                ...item,
                uuid: generateUUID(),
                quantity: 1,
                properties: {
                  ...item.properties,
                  "_hey": "This is a test property",
                },
              },
              {
                ...item,
                quantity: item.quantity - 1,
              }
            );
          } else {
            acc.push(
              {
                ...item,
                uuid: generateUUID(),
                quantity: 1,
                properties: {
                  ...item.properties,
                  "hey": "This is a test property",
                },
              },
              {
                ...item,
                quantity: item.quantity - 1,
              }
            );
          }
        } else {
          acc.push(item);
        }
        return acc;
      }, [] as typeof cart.lineItems);

      api.cart.bulkCartUpdate({
        ...cart,
        lineItems: lineItemsUpdated
      })
    } catch (error) {
      console.error("Error adding product to cart:", error);
    }
  };

  return (
    <Screen name="Cart Items" title="Cart Line Items">
      <ScrollView>
        <Stack direction="vertical">
          {cart.lineItems.length === 0 ? (
            <Section>
              <Text>No items in cart</Text>
            </Section>
          ) : (
            cart.lineItems.map((item) => (
              <Section key={item.uuid}>
                <Stack direction="vertical">
                  <Text variant="headingLarge">{item.title}</Text>
                  <Text>{item.uuid}</Text>
                  <Text>Quantity: {item.quantity}</Text>
                  {item.price && <Text>Price: ${item.price.toFixed(2)}</Text>}
                  {item.sku && <Text>SKU: {item.sku}</Text>}

                  {/* Display existing properties if any */}
                  {item.properties &&
                    Object.keys(item.properties).length > 0 && (
                      <Stack direction="vertical">
                        <Text variant="headingSmall">Properties:</Text>
                        {Object.entries(item.properties).map(([key, value]) => (
                          <Text key={key}>
                            {key}: {value}
                          </Text>
                        ))}
                      </Stack>
                    )}

                  {/* Add property section */}
                  <Stack direction="vertical">
                    <TextField
                      label="Add custom note"
                      value={propertyValues[item.uuid] || ""}
                      onChange={(value) =>
                        handlePropertyInputChange(item.uuid, value)
                      }
                      placeholder="Enter a custom note"
                    />
                    <Button
                      onPress={() => handleAddProduct(item.variantId, false)}
                      title="Add hey property to one unit of this item"
                    />
                    <Button
                      onPress={() => handleAddProduct(item.variantId, true)}
                      title="Add _hey property to one unit of this item"
                    />
                  </Stack>
                </Stack>
              </Section>
            ))
          )}

          <Button onPress={() => api.navigation.dismiss()} title="Close" />
        </Stack>
      </ScrollView>
    </Screen>
  );
};

export default reactExtension("pos.home.modal.render", () => (
  <SmartGridModal />
));

Thanks,

JV

1 Like

Hi JV,

It’s possible this may be a limitation of how hidden properties appear in carts - if you do a test checkout, is the hidden property applied to all items in the order?

1 Like

Hey Liam, thank you for the response on this! Through cart transform functions logging I can see the hidden property applied to all items. Is there any way to apply it to only one item?

1 Like

Hi again JV,

From digging into this more, it looks like there’s is no supported way to have a hidden property (_-prefixed) apply to only one unit of a split line in the Shopify cart. This is a limitation of Shopify’s cart merging logic, and there’s currently no workaround using Shopify Functions or the POS/cart API. If you need per-unit hidden data, you’ll need to use visible properties and handle the visibility yourself.

1 Like