There is no separate "menu" on mobile. The point of sale is the menu: it reads the same sf_product catalog as the storefront, then applies a per-location overlay for what is on today and at what price. This page is what to set up so the grid behaves.
Product fields the POS uses
Products are sf_product records, maintained in Storefront. The mobile POS loads them with GET /storefront/products (paged, 50 at a time) and categories with GET /storefront/pos-categories.
| Field | Used for |
|---|---|
name, image, images[] | The card. A product with no image shows its name in the tile instead. |
sku | Lookup by barcode or scan, and the line's identity on the order. |
price | Base unit price. |
taxable, tax | Whether the line feeds the tax base. Non-taxable lines (fees, deposits) are excluded when the server prices the order. |
stock, available, sold | Stock on hand. Shown on the tile when present. |
attributes[] | Modifier groups. Each option can carry a priceDelta. |
variations[] | Concrete SKUs matched on every option group (size and colour, for example), each with its own price and stock. |
status | Only active products sell. |
salesChannels[] | Which channels list it. |
workflow | Overrides the preparation pipeline for this product. |
Modifiers and stations
Options come from attributes[]. Two are special to the kitchen:
- A
prepStationoption (KITCHEN, BAR, BARISTA…) on a product or on the chosen variation is copied onto the order line when it is fired, and the Pipelines screen filters by it. Set it on everything that goes to a screen, or the station filter has nothing to group by. courseandseatare entered per line at the POS, not on the product.
Day-parts and the 86 list
More → Menu (day-parts & 86-list) is where you take things off sale for a location and put them back. It reads the day-parts and the 86 flag; it does not edit day-part membership or price overrides — those are set on the overlay record, from the web. The day-parts are fixed in the app:
| Id | Label |
|---|---|
all | All day |
dinner | Dinner |
brunch | Brunch |
late_night | Late night |
happy_hour | Happy hour |
The POS shows these as chips above the grid. "All day" lists everything; each other chip lists the products whose overlay includes that day-part. An 86'd product is dimmed and cannot be added until it is un-86'd, and the reason you typed is shown to staff.
Taking an item off sale
- More → Menu (day-parts & 86-list). Check the Location at the top is the one you mean — the header says Pick a location to manage its menu until one is chosen.
- Find the item. Search items narrows by name; the day-part chips narrow by menu; the summary line reads
items · .86'd - Switch the item off.
- Type a Reason (optional) — Out of prep, supplier delay… — and tap 86 it.
You will see
To put it back: switch it on. It confirms
From the POS instead: long-press a product tile to 86 or un-86 it without leaving the sale. Same records, same effect everywhere — this is the faster path mid-service.
If it will not save, the screen says Could not update — try again. It is a failed write, not a rejected one; retry, and check the connection before assuming the item is off sale.
The per-location overlay
Day-part membership, 86 status and a price override are not on the product. They live on a small overlay record per product per location, bm_location_product, which the app reads with POST /repository/find/bm_location_product and writes with POST /repository/update-partial/bm_location_product/:sk.
| Overlay field | Effect at the POS |
|---|---|
eightySixed, eightySixReason | Dims the tile; blocks adding it. |
priceOverride | Replaces sf_product.price for this location. |
menuIds[] | Which day-part chips list it. |
bm_location_product records exist and the app reads and writes them, but there is no schema for the datatype in the SDK and no server-side logic behind it. Treat it as a planned, unpublished schema: it works as a plain repository collection, nothing validates it, and an organization with no overlay records simply sees every product on every day-part at catalog price. The demo organization is in that state.
Stock
Stock is per product (and per variation) and is decremented by the storefront order flow when a tab settles, the same way an online order does. The POS shows the number on the tile so staff see what is left, and a product that reaches zero should be 86'd from the menu editor rather than left to disappoint at the counter — the tile does not hide itself.
A location-specific stock figure is one of the things the overlay is intended to carry; until that exists, the number on the tile is the catalog-wide figure.
Pricing is decided on the server
The app sends lines; the server prices the order. priceOrder runs on every item change and again on settle: taxable lines feed the tax base, an order-level discount is apportioned across that base, and the total is rounded to cents. That is why a tab's amount can change slightly between adding items and settling, and why editing a price in the app is not a thing — change it in Storefront or the overlay.
Ad-hoc lines are the exception. A Take payment charge writes a single QUICK-SALE fee line with its own amount straight onto the order, and the app writes the subtotal and total alongside it because nothing else recomputes money for that path.
When something goes wrong
| Symptom | Cause | Fix |
|---|---|---|
| Pick a location to manage its menu | No location selected | Choose one in the header. The menu is per location. |
| No items in this menu | The day-part chip or search excludes everything | Switch to All day and clear the search. |
| Could not update — try again | The 86 write did not reach the server | Retry. The item is still on sale until it confirms. |
| An item is 86'd on one device only | That device is on a different location | Check the location picker on both. |
| Every product shows under every day-part | The organization has no overlay records | Expected until day-parts are set up on the web; the app filters on what exists. |
| A product will not add and shows a reason | It is 86'd | Long-press it in the POS to bring it back, or sell something else. |
| A product will not add with no reason | It is out of stock, inactive, or not on this sales channel | Fix the product in Storefront; the POS only sells active, in-stock items. |
| The station filter in Pipelines is empty | Products carry no prepStation | Set it on the product or variation; fired lines copy it. |
| The price on the tile is not what you expect | A location price override, or a catalog change | The server prices the order — read the total at checkout. |