DataMosaic

Blog · Fix a sync

Square inventory not updating: 7 causes and a per-store test

The sale went through and the count stayed where it was. Seven causes, what each one leaves behind, and one export that shows them across every store.

#What Square promises, and where it breaks

Square's own wording is simple: once tracking is on, "the stock count will update based on sales from your Point of Sale app", and the same goes for Square Invoices and orders shipped through your online store. Stock is kept per location, so a chain of stores has one count per item per store.

Every cause below breaks one part of that promise: the item, the sale, or the location. The causes are close to the ones in the post on Shopify POS not updating inventory, because a till is a till, but the screens, the export and two of the causes are Square's own.

#The 7 causes

#1. The item or variation isn't tracked

Stock only moves for an item with tracking switched on. In Square Dashboard, go to Items & services > Items > Item library (on some accounts the menu reads Items & inventory or Items & menus), open the item and, under Inventory, switch Track stock on. For an item with variations, each variation has its own Advanced Stock Tracking switch under Variations. One size tracked and the others not is a common way for half a range to stop counting.

Square also says that turning tracking on "will always require the item to have a stock count", so an item switched on in a hurry often starts at a number nobody counted.

How to see it: the variation shows no count in the export, or the same count night after night while the store keeps selling it.

#2. The sale was a keypad amount

The Point of Sale app lets staff tap Keypad and enter a custom amount. That is an amount with a note, not an item from the item library, so there is no item whose stock could move. Staff reach for the keypad when a barcode won't scan or an item won't come up in search, and the real item's count stays where it was.

How to see it: the difference matches custom amounts in that store's transactions on the day. The stock is too high by exactly the units sold that way.

#3. Payments are waiting on an offline device

With Offline payments switched on, a device keeps taking card payments when the internet drops, and uploads them "automatically once the device is back online". Until they upload, Square has not received those sales. Square Register, Terminal and Handheld must reconnect and upload within 72 hours; pending offline payments expire after that.

The bigger risk is what staff do while waiting. Square warns not to sign out of the app, delete it, switch modes or locations, or factory-reset a device with offline payments pending: they "will be permanently lost and the funds won't be captured." A sale that never uploads never moves the stock either.

How to see it: the difference equals one device's sales during an outage. It clears when the device reconnects, or never does if the payments were lost.

#4. A return skipped the restock step

In a return, the app asks you to select item(s) to restock or tap Skip this step. Tap Skip this step for an item that goes back on the shelf and Square's count stays one lower than the shelf for good. The reverse happens too: a damaged item restocked at the till is counted as sellable.

An Unlinked refund offers Refund items or Custom amount. A custom amount refund, like a keypad sale, names no item.

How to see it: the difference matches a return or refund in that store's transactions, and the item's stock history shows no restock for it.

#5. An import replaced the counts

The fastest way to break every count at once is a bulk import. Square's import page is plain: "Any changes made to the New Quantity column will replace the existing item quantity", and entering zero sets that item and location to zero. Someone exports the library on Monday to fix prices, fills in New Quantity while they are at it, imports on Thursday, and three days of sales disappear from the counts.

How to see it: many items at many stores change at the same minute in the stock history, all set to a figure rather than adjusted by one.

#6. You are reading available, not on hand

Square keeps two numbers. On hand is the stock in the store. Available is on hand minus stock committed to open bills, saved baskets and scheduled orders. A basket saved at the till and never closed holds its units back from available without anything having sold.

How to see it: the count you trust matches on hand, and the difference against available equals open bills or saved baskets at that store.

#7. Another app writes the same count

Square takes stock changes from more than its own app: an online store, an ERP connector, a warehouse system or a marketplace app can all write counts through Square's API. Two of them writing the same item means the last one wins, and a connector that sets a count rather than adjusting it undoes every sale since its last run. The causes on the connector side, from SKUs that don't match to errors nobody reads, are the same as in the post on Shopify inventory not updating.

How to see it: the count jumps to a round figure at the time the connector runs, and the stock history shows the adjustment with no sale or count behind it.

#Read the stock history first

Before you change a count, find out what changed it. In Square Dashboard, go to Items > Inventory management > History, or open one item and click View stock history under Variations. Filter by adjustment type and location; Actions > Export History saves it as a file. Square lists stock adjustment history with Square for Retail Plus and Premium, Square for Restaurants Plus and Premium, and Square Plus or Premium with advanced inventory.

To see one item across stores, go to Items & services > Inventory management > Stock overview, filter by one or more locations, and read On Hand.

#Line up every store at once

One item tells you it is wrong. Every store at once tells you why, because causes 2, 3 and 4 show up at one store and cause 5 at all of them.

  1. In Square Dashboard, open Item library (or Stock overview).
  2. Click Actions > Export Library.
  3. Open the file. With more than one location it has location-specific headers, one set per store; the count Square holds now is under Current Quantity. Square ignores that column on import, so reading it is safe.
  4. Save it as square_export.csv.

The other side is the count you trust for each store: the ERP's stock, if it books the stores' sales from somewhere other than Square, or the store's last count. Save it as counted.csv with three columns, sku, location and on_hand, and name the locations as Square does.

This script finds the header row, reads every column whose header starts with Current Quantity, takes the rest of the header as the store, and prints every SKU and store where the two differ, plus the rows on one side only:

import csv
import sys
from collections import defaultdict

PREFIX = "Current Quantity"  # change it if your export's headers differ


def number(text):
    text = (text or "").strip()
    if text == "":
        return None
    try:
        return int(float(text))
    except ValueError:
        return None


def load_square(path):
    counts, flags = {}, {}
    with open(path, newline="", encoding="utf-8-sig") as f:
        rows = list(csv.reader(f))
    start = next(i for i, r in enumerate(rows) if "SKU" in r)
    header = rows[start]
    sku_col = header.index("SKU")
    qty_cols = [(i, h[len(PREFIX):].strip(" -:[]") or "all")
                for i, h in enumerate(header) if h.startswith(PREFIX)]
    for row in rows[start + 1:]:
        if len(row) <= sku_col or not row[sku_col].strip():
            continue
        sku = row[sku_col].strip()
        for i, loc in qty_cols:
            cell = row[i] if i < len(row) else ""
            qty = number(cell)
            if qty is None:
                flags[(sku, loc)] = cell.strip() or "blank: not tracked?"
            else:
                counts[(sku, loc)] = qty
    return counts, flags


def load_counted(path):
    counts = defaultdict(int)
    with open(path, newline="", encoding="utf-8-sig") as f:
        for row in csv.DictReader(f):
            sku = row["sku"].strip()
            if sku:
                counts[(sku, row["location"].strip())] += int(row["on_hand"])
    return counts


square, flags = load_square(sys.argv[1])
mine = load_counted(sys.argv[2])

print("sku,location,square,counted,difference,note")
for key in sorted(set(square) | set(mine) | set(flags)):
    sku, loc = key
    if key in flags:
        print(f"{sku},{loc},,{mine.get(key, '')},,{flags[key]}")
    elif key not in square:
        print(f"{sku},{loc},,{mine[key]},,missing in Square")
    elif key not in mine:
        print(f"{sku},{loc},{square[key]},,,missing in your count")
    elif square[key] != mine[key]:
        print(f"{sku},{loc},{square[key]},{mine[key]},{square[key] - mine[key]},")

Run it as python compare_square.py square_export.csv counted.csv > differences.csv. Save the export as CSV from Excel with care: a SKU like 00412 loses its leading zeros on the way, and then nothing matches. If your stores are named differently on the two sides, rename them first; the CSV comparison post covers keys that almost match.

#A worked example

An invented chain of 120 stores runs Square Point of Sale at every till. The ERP books each store's stock from goods received and a weekly count. One Tuesday morning, the script returns these rows:

SKULocationSquare on handCountedDifference
TEE-BLK-MST-014149+5
MUG-WHTST-022118+3
SCARF-REDST-03145−1
CAP-NVYST-0376blank: not tracked?
every SKUST-0040 to 40various212 rows

Invented figures. Read the patterns, not the numbers:

  • ST-014, +5. Five T-shirts went through on the keypad on Saturday, when the label wouldn't scan (cause 2). Adjust the count, then fix the label.
  • ST-022, +3. The Wi-Fi dropped for an afternoon and a till signed out with offline payments pending (cause 3). The three mugs left the shop; the sales never reached Square.
  • ST-031, −1. A scarf was returned with Skip this step, and went back on the rail (cause 4).
  • ST-037, blank. CAP-NVY has variation tracking switched off at the medium size only (cause 1). Every sale of it has had nothing to reduce.
  • ST-004, 212 rows. All set at 16:02 on Thursday by an import whose New Quantity column came from Monday's export (cause 5). The stock history shows one adjustment per item, at the same minute.

Five of the seven causes, found in one export, and none of them visible from a single item page.

#Doing it every night

The export and the script take a few minutes. The trouble is that the differences come back: another keypad sale on a busy Saturday, another outage, another import. The first person to notice is usually a customer told an item is in stock at their store.

DataMosaic runs that comparison every night against the systems themselves. It reads Square the way you can reach it: the library export as a file it fetches from a folder or an SFTP server, or Square's API through an API source with a bearer token. A check sets Square's count per SKU and store against the ERP's; its rules say how far apart is too far. A row that differs offers an investigation, so a store that is five up on one SKU points at keypad sales rather than at a stock-take. If you want Mosaic to act, say by emailing the store manager when the same store differs two nights running, that is a workflow you build.

The same idea, comparing at a grain both systems keep, is how POS and ERP sales are checked each morning.

#Questions people ask

#Why is my Square inventory not updating after a sale?

Most often the item or variation isn't tracked, the sale went through on the keypad rather than as an item, or offline payments haven't uploaded yet. Export the item library and compare it per SKU and store to see which.

#Do keypad sales reduce inventory in Square?

No. A keypad sale is an amount, not an item from your item library, so there is no item whose stock could move. Ring up the item instead, or add it to the library first.

#Does Square update inventory when offline?

Offline payments upload automatically when the device reconnects, so the sales reach Square then. Don't sign out, switch location or reset the device while payments are pending, or they are lost.

#Why did my Square stock counts reset after an import?

The New Quantity column in an imported library file replaces the existing count. A file exported days earlier with that column filled in sets every item back to the older figure.

#Does a refund put stock back in Square?

Only if you restock it. In a return, select the items to restock rather than tapping Skip this step; a custom amount refund names no item, so nothing goes back.

Stop comparing exports by hand.

DataMosaic checks the systems themselves overnight, tells you why they differ, and fixes it before anyone else notices.