Feature Request: Background Actions & Non-Blocking Cart Operations for POS UI Extensions

Summary

We are requesting the ability for POS UI Extensions to perform “background” or non-blocking actions, particularly for complex addToCart sequences. Currently, high-volume merchants experience significant friction because the main UI thread (or at least the user flow) is blocked while the extension performs necessary data manipulation (splitting lines, attaching attributes) and waits for Shopify Functions (Cart Transform) to execute.

Context & Use Case

Our POS integration powers complex retail workflows (e.g., cafes, quick-service restaurants) where speed is critical. A common workflow involves:

  1. Customisation: User selects complex modifiers for a product (e.g., “Latte” with “Soy Milk”, “Extra Hot”, “Vanilla Syrup”).
  2. Add to Cart: The extension creates a line item with specific properties.
  3. Transformation: We use the Cart Transform API (Shopify Functions) to split this single “Latte” line item into its component parts for inventory tracking (1x Latte, 1x Soy Milk Surcharge, 1x Vanilla Syrup).
  4. Repeat: The user often taps “Add and Order More” to immediately start customising the next item for the same customer.

The Problem

The current architecture forces a synchronous wait on every “Add to Cart” action.
When a user taps “Add to Cart”:

  1. The Extension calculates payload.
  2. cart.lines.add or cart.bulkUpdate is called.
  3. Network request to Shopify.
  4. Cart Transform Function runs (Backend logic).
  5. Cart is recalculated and returned to POS.
  6. Only then is control returned to the user.

For a busy barista ringing up 5 drinks in a row, waiting ~500ms-1s+ per item for the cart execution to settle is a significant degradation in UX compared to native POS speed.

Proposed Solution

We propose two potential enhancements to the POS UI Extensions API:

1. Optimistic / Background addToCart

Allow extensions to flag a cart operation as “background” or “optimistic”.

// Proposed API Sketch
api.cart.lines.add(lines, { background: true });

Behavior:

  • The promise resolves immediately (or very quickly), allowing the UI to unblock.
  • The actual cart update and recalculation happens asynchronously.
  • If an error occurs (e.g., out of stock), a global toast or error banner notifies the user after the fact, but the happy path is instant.

2. POS Background Workers

Introduce a “Headless” or “Background” target for POS Extensions that can process a queue of tasks.

  • Use Case: The UI Extension pushes a “job” (e.g., { type: 'ADD_COMPLEX_ITEM', payload: ... }) to the worker.
  • The UI Extension immediately resets its form for the next item (“Add and Order More”).
  • The Worker processes the job in the background, making the necessary API calls to update the cart.

Why this matters

  • Parity with Native Performance: Native POS buttons feel instant. Extensions feel sluggish by comparison when doing anything non-trivial.
  • High-Volume Compatibility: This feature is essential for QSR (Quick Service Restaurant) and high-volume retail environments where every second counts.
  • “Add and Order More” Flows: This specific UI pattern relies heavily on the ability to “fire and forget” the previous item to focus on the next one.

Example Code (Current vs Desired)

Current Blocking Flow

const handleAddToCart = async () => {
  setIsLoading(true); // Blocks UI
  
  // 1. Prepare complex attributes
  const attributes = calculateAttributes(state);
  
  // 2. Perform blocking update
  await api.cart.bulkUpdate({
    lineItems: [...currentItems, newItem] 
  }); 
  // User waits here... ⏳
  
  setIsLoading(false);
  closeModal(); // User finally free to move on
};

Desired Non-Blocking Flow

const handleAddToCart = () => {
  // 1. Prepare complex attributes
  const attributes = calculateAttributes(state);
  
  // 2. Fire and forget (Background Action)
  api.cart.bulkUpdate({
    lineItems: [...currentItems, newItem] 
  }, { mode: 'background' }); 
  
  // 3. Immediate return to user
  resetForm(); // User immediately starts next item 🚀
  toast.show("Item added in background");
};

We believe this functionality would unlock a new tier of professional, high-speed apps on Shopify POS.

2 Likes

Hi @adamwooding

Can you try removing the await and see if this dismisses the model?

Hi @Liam-Shopify ,

Thanks for the suggestion regarding removing await. We have reviewed our implementation, and unfortunately, removing await is not possible with the current POS UI Extensions API for our use case.

Here is the technical reason why:

Our “Add to Cart” flow is not a single atomic operation. Because we support complex product customisations (modifiers, surcharges, etc.) that rely on Shopify Functions (Cart Transform), we must attach custom attributes (properties) to the line item at the moment of creation.

The current POS Extension API forces us into a multi-step, sequential dependency:

  1. Step 1: We call await api.cart.lines.add(...) (or addLineItem).
  • Reason we must await: We need this call to resolve because it returns the UUID of the newly created line item.
  • We cannot proceed without this UUID.
  1. Step 2: We call await api.cart.lines.addProperties(uuid, properties).
  • Reason we must await: We must attach the specific attributes (modifiers, choices) to that specific UUID.
  • If we do not wait for Step 1, we don’t have the UUID.
  • If we “fire and forget” Step 1, we have no way to guarantee Step 2 runs successfully before the user proceeds.

The Consequence of removing await:

If we remove the await on Step 1, the code execution continues immediately. we would attempt to call addProperties with an undefined UUID, causing the entire customization to fail. The item would be added to the cart as a “base product” without any modifiers, leading to:

  • Incorrect Pricing: Surcharges for modifiers are missing.
  • Incorrect Inventory: Our Cart Transform function acts on the properties to split the item. Without properties, the specific inventory items (e.g. “Soy Milk”) are never deducted.
  • Wrong Orders: The kitchen receives a generic “Latte” instead of a “Latte with Soy Milk”.

Why the “Optimistic” suggestion doesn’t work: We currently implement an optimistic UI where possible (we update local state), but we cannot unblock the main thread until this “Add → Get UUID → Update Props” 1-2 punch is complete.

This is precisely why we are requesting a Background/Batch API. We need a way to say: “Here is the Item AND its Properties. Please add them together in the background and let me know when you’re done.”

Until the API supports atomic creation of items with properties, or a background worker that can handle this chain, we are forced to block the UI to ensure data integrity.

@adamwooding Thank you for providing the details here. I wasn’t aware you had multiple steps. I think your feature request is a cool idea.

1 Like

Hey @JS_Goupil ,

Thank you so much for your reply :slight_smile:

Please let me know if you have any questions or if there is anything I can provide to help with this. It would be incredible to see this feature become a reality :folded_hands:t5:

Thank you again,

Adam