Line Item not distinct with line item properties

Hello,
i discovered that when i add a line item property to a cart line and then add the same product again it stacks on the existing item instead of being added as a distinct item, as it is done in the online store. Is this expected behavior? If it is, how can i have a product multiple times with different properties?

Hey @andre-hyghstreet :waving_hand: - I did a bit of digging into this, and you’re right, if a line item property is different, even on the same product variant it should be set up as a separate line item (this is expected behaviour in the online store as well as through our Storefront API).

Can you share how you’re setting up the carts in your POS Extension? If you can share the step by step process you’re using with the POS Extension’s Cart API, I can try to replicate things on my end here and investigate further. Hope to hear from you soon!

Hi @Alan_G im just applying a property to any item in the cart. And after that adding the same product again. This is the whole source:

import React, { useEffect, useState } from 'react'

import { Text, Screen, ScrollView, Navigator, reactExtension, useScannerDataSubscription, useApi, useCartSubscription, Box, List, ListRowSubtitle, TextField, NumberField, Button } from '@shopify/ui-extensions-react/point-of-sale'
import { LineItem } from "@shopify/ui-extensions/point-of-sale";

const Modal = () => {

  const cart = useCartSubscription();
  const api = useApi()

  const Currency = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" })

  return (
    <Navigator>
      <Screen name="Lines" title="Warengruppenartikel">
        <List title="Products" data={cart.lineItems.map(line => {
          return {
            id: line.uuid,
            leftSide: {
              label: String(line.title),
              subtitle: [{content: Currency.format(Number(line.price))}] as [
                ListRowSubtitle,
              ],
            },
            rightSide: {
              label: 'Edit price',
              showChevron: true,
            },
            onPress: () => api.navigation.navigate('Details', { line: line })
          }
        })} />
        
      </Screen>
      <DetailsScreen/>
    </Navigator>
  )
}

const DetailsScreen = () => {
   const [params, setParams] = useState< { line: LineItem }>();
   const initialPrice = String(params?.line.price ?? "0.00") 
   const api = useApi()
   const [price, setPrice] = useState<string>(initialPrice);

  useEffect(() => {
    setPrice(initialPrice)
  }, [params])

   return <Screen 
    name="Details" 
    title="Details"
    presentation={{sheet: true}}
    onReceiveParams={setParams}>
        {
          params 
          ?  <ScrollView>
                <TextField
                  label="Price"
                  placeholder="19,99"
                  helpText="Enter the price of the product"
                  // inputMode="decimal"
                  value={price}
                  onChange={(value) =>setPrice(oldValue => value.replace(',', '.'))}
                  required={true}
                  action={{
                    label: 'Clear',
                    onPress: () => setPrice(''),
                  }}
                />
                <Button title="Apply" onPress={() => {
                  api.cart.addLineItemProperties(params.line.uuid, { "_custom_price": String(price) })
                  api.toast.show('Price applied.')
                }} />
              </ScrollView>
          : <ScrollView>

          </ScrollView>
        }
     
    </Screen>
}

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

Thanks @andre-hyghstreet, appreciate you sending that my way. I’ll do some more digging into this and loop back with you here when I have next steps :slight_smile:

Hey @andre-hyghstreet :waving_hand: - just following up here as promised. I was able to speak with some folks on my end here and can confirm duplicate line item splitting isn’t supported at the moment in the POS extensibility framework (even if they have separate properties).

That said, it is on the radar to be implemented in the future - I can’t guarantee an exact timeline, but it’s something that we’re looking at making available.

Hope this helps a little bit at least.

@Alan_G Do you have any updates on this? This is creating some awkward conversations between me and a client I’m trying to sell Shopify to. Do you know of any work arounds at the moment?

Basically, we just need to provide the ability to add DIFFERENT custom line item properties for multiple of the same SKU in the POS app. ie: Kathy and Judy both want the Grey, Small Scrubs with their names embroidered on it and want to use the company card. With this bug, they have to do separate transactions.

The happy path works fine, but right now we have sales associates pushing real customers into multiple in person transactions for custom embroidery.

Hi @D2C-Dev

I noticed there was an update in the recent Winter Edition that might improve this experience, from our docs:

If you have an app installed using POS UI Extensions to collect additional information about a product, you can sell different configurations of the same product, within the same cart. For example, customized picture frame sizes or multiple coffee orders with different names.

Would that solve this for you?

Hi all,

I’ve been experimenting with this and I believe splitting line items that have different properties is definitely possible in POS now.

Here is how I managed to get it working.

Method 1: Sequential Adding

You can combine addLineItem with addLineItemProperties to separate them right from the start.

// 1. Add the base variant (ID: 55443322) to the cart
// This generates a line item. Let's assume the UUID becomes 'uuid-original-A'
api.cart.addLineItem(55443322, 1);

// 2. Attach a specific property to that unique line item UUID
api.cart.addLineItemProperties('uuid-original-A', { Monogram: 'JS' });

// 3. Add the same variant again
// This generates a NEW line item. Let's assume UUID is 'uuid-new-B'
api.cart.addLineItem(55443322, 1);

// The cart now treats these as distinct entries because their properties differ.
// Resulting State:

| Line Item UUID | Variant ID | Qty | Properties |
| :--- | :--- | :--- | :--- |
| uuid-original-A | 55443322 | 1 | Monogram: JS |
| uuid-new-B | 55443322 | 1 | *None* |

// Note: If you were to add { Monogram: 'JS' } to 'uuid-new-B' later,
// POS would automatically merge them back into a single line item with Qty 2.

Method 2: Splitting Existing Items via bulkCartUpdate

If you have a line item with a quantity greater than 1 and need to split it programmatically, you can use a reducer to rebuild the line item array.

The Scenario: Imagine you have a cart where Item uuid-original-A has a Quantity of 3, and you want to separate one of them to add a property.

const currentCart = useCartSubscription();

// The UUID of the line item we want to target
const targetUuid = 'uuid-original-A';

// Build a new array of items
const newCartState = currentCart.lineItems.reduce((list, row) => {
  
  if (row.uuid === targetUuid) {
    // We found the target. Return an array injecting the split items.
    return [
      ...list,
      // 1. Keep the original item but decrement quantity by 1
      {
        ...row,
        quantity: row.quantity - 1
      },
      // 2. Create the split item with Quantity 1 and the new Property
      {
        ...row,
        uuid: "new-uuid-generated-explicitly", // It's best to supply a fresh UUID here
        quantity: 1,
        properties: { Monogram: 'JS' }
      }
    ];
  }

  // If it's not the target, just push it to the list unchanged
  return [...list, row];
}, []);

// Commit the changes to the cart
api.cart.bulkCartUpdate({
  ...currentCart,
  lineItems: newCartState
});

// Final Cart Result:
| Line Item UUID | Variant ID | Qty | Properties |
| :--- | :--- | :--- | :--- |
| uuid-original-A | 55443322 | 2 | *None* |
| new-uuid-gener... | 55443322 | 1 | Monogram: JS |
| [other-items]  | ... | ... | ... |