Scanner API is returning stale data, again. v2026-01

Hello there.

I’m migrating from 2025-07 to 2026-01 and I’m not only moving every single component from the react package to the polaris components (how fun and productive!) one of my favorite buggy behaviors are back.

If you use the

  useEffect(() => {
    const unsubscribeData = shopify.scanner.scannerData.current.subscribe((result) => {
      setScanData(result.data || '');
      setScanSource(result.source || '');
    });

    const unsubscribeSources = shopify.scanner.sources.current.subscribe((sources) => {
      setHasCameraScanner(sources.includes('camera'));
      setHasExternalScanner(sources.includes('external'));
    });

    return () => {
      unsubscribeData();
      unsubscribeSources();
    };
  }, []);

and get the scan data, you can do stuff with it. Nice.

And if you unmount this scanner component and mount another one, in the same extension/modal, if you go back to the scanner component with a new mount with the intention of scanning something new again, the current subscription already returns the old/stale scan data.

Even tho I’ve already called the unsub function, there’s stale data from a scan that is 5 minutes ago, still being returned in the current object.

Now I have to write a custom scanner state management on top of your scanner subscribeable because it’s not being cleared properly.

bump bump bump anyone here?

Is this topic the same issue?

hey @Dylan, yes and no.

because your original post is/was outdated, as this issue was resolved (at least until now v2026-01 reintroduces it). commented on your post to bump it up and wanted to get my own going for more visibility.

Ah gotcha, thanks. I didn’t even realize it was temporarily fixed. But I’m in the same boat, I need to refactor to the polaris components.

Good luck with the polaris migration :vulcan_salute:

22 days and no replies from any shopify staff?

Here’s an update.

We primarily use the camera as the source for the scanner from our iPads.

The issue mentioned here seems to be mainly with the camera source of the Scanner API, as one location with a physical scanner seems to be doing fine. The scan is maybe tied to a trigger of some sort with a physical scanner as opposed to camera just immediately returning stale data?

Idk if this helps.

Hi @gunes, thanks for reporting this — and apologies for the delayed response.

We’ve confirmed this is a bug on our side. When a component within your extension unmounts and remounts (e.g., closing and reopening a modal), the scanner subscription re-delivers the last scan result because the underlying state is never cleared. This affects all API versions, not just 2026-01.

We’ve filed internal issues to track the fix. In the meantime, here’s a workaround you can use — skip the initial emission on subscribe, since a real scan can’t fire in the same tick as the subscription:

useEffect(() => {
  let isFirstEmission = true;

  const unsubscribeData = shopify.scanner.scannerData.current.subscribe((result) => {
    if (isFirstEmission) {
      isFirstEmission = false;
      if (result.data) return; // skip stale value from previous scan
    }
    setScanData(result.data || '');
    setScanSource(result.source || '');
  });

  const unsubscribeSources = shopify.scanner.sources.current.subscribe((sources) => {
    setHasCameraScanner(sources.includes('camera'));
    setHasExternalScanner(sources.includes('external'));
  });

  return () => {
    unsubscribeData();
    unsubscribeSources();
  };
}, []);

The logic: when your component mounts and subscribes, the first callback fires immediately with whatever value is currently in state. If that value already has data, it’s stale from a previous scan — no real scan could have happened in the same tick as your subscription. So we skip it and only process subsequent real scan events.

We’re evaluating a proper fix (likely a clearScannerData() API method) so you won’t need this workaround long-term.

Victor

1 Like

hey @Victor_Chu, thank you for the response!

I’m already doing something along these lines to work around the stale first emission, but thanks for the suggestions.

Ideally, it would be great if we didn’t need to call methods manually like clearScannerData() (though having it as a fallback wouldn’t hurt). If scannerData.current could always return fresh data, that would remove the need for this entirely.

Anyway, thanks again for the reply! Looking forward to a fix, hopefully soon! :slightly_smiling_face:

Hi @Gunes , just wanted to check in and ask a question to clarify. In your case, when you’re saying the scan was “5 minutes ago”, during that time was the extension instance killed and restarted? ie did the merchant close the extension modal and reopen it? In that case, there will be a fix for 2026-04. But if the modal is open and you’re unsubscribing, and resubscribing within the same modal instance, then you should expect the last value to still be there as you’re still interacting with the same underlying state.

hey @JS_Goupil,

during that time was the extension instance killed and restarted?

yes, the scan could be as long as 5 minutes ago.

the extension instance is not killed or restarted. by that, do you mean closing the entire shopify pos app? if not, we don’t know a way to explicitly kill or restart a tile/extension instance.

did the merchant close the extension modal and reopen it

there is no merchant in this case, it’s a custom in-house extension. and no, the modal isn’t closed and reopened, since you can scan multiple items per instance after opening the tile.

the issue happens when navigating between views and returning to the scanner. the scanner immediately emits the previous scan data.

imagine this workflow:

  1. scan a product
  2. scanner data emits the barcode, we find the product, and navigate to the product detail page to add to cart
  3. the product detail page has an option to “add to cart and scan another item”
  4. on button press we go back to the scanner view
  5. immediately on returning, we get the previous scan data again (stale), without a new physical scan

this is a problem because there’s view state and flow logic tied to whether scanner data is present or not, and the stale emission triggers that logic.

you’re unsubscribing, and resubscribing within the same modal instance, then you should expect the last value to still be there

i think this is the key disagreement/expectation mismatch. from a developer perspective, an API named scannerData.current implies the current scan event or fresh scan data, not the last emitted value from a previous scan when no new scan occurred.

and this expectation does not come from an assumption, it used to work exactly this way until 2025-07 (we had to skip 2025-10 so i dont have a reference there), we had a hiccup in 2024 i think when there was another stale scanner data incident, but that was resolved, hence the word “again” in the title my post. (old github issues are also gone so i cannot reference it)

without a new scan, receiving old data makes it hard to reason about state and leads to unintended behavior.

happy to clarify more if helpful, and thanks again for looking into it.

@Gunes thanks for the detailed feedback. I see how this is a frustrating DX. scannerData.current is actually a representation of an internal POS state and so clearing the “local” state (what we forward to your extension) would be messy as we don’t have anything clear to base that logic on. Unless of course, we come up with a clear and predictable mechanism which I think you’re alluding to.

Let me just play back to you what I think you’re suggesting in this entire thread and tell me if I’m missing a detail:

  1. Clear the scannerData.current when unsubscribe() is called. Pretty straight forward, you call `scannerData.current.subscribe((data) => {})` again later, you don’t immediately get your callback triggered with the last value that was there before you unsubscribed.
  2. Provide a shopify.scanner.clearData() (naming pending) function which can be used as a fallback. This would work well for those who are using the Preact bindings (this makes scannerData.current.value is stateful). This is to complement the lack of subscribe/unsubscribe mechanism.

Would this resolve the problems for you and your team?

Thanks again for the patience and constructive feedback!

1 Like

yes #1 would already be great and #2 would be the cherry on top for even more control.

thanks for the quick reply and for already suggesting possible resolutions to the issue!