{"title":"Upgrade guide — v21 to v22 — POS","slug":"upgrade-guide-v21-to-v22-pos","url":"https://support.storeconnect.com/articles/upgrade-guide-v21-to-v22-pos","url_markdown":"https://support.storeconnect.com/articles/upgrade-guide-v21-to-v22-pos.md","subtitle":null,"summary":"Upgrade steps for POS users moving from v21 to v22, covering the removed Layouts settings section, payment settings read from synced store variables, fulfillment station objects, print template price formatting, and the opt-in open orders, fast checkout, tipping, and smart cash features.","type":"Release_Notes","video_url":"","keywords":null,"last_modified":"2026-10-23T00:01:31+0000","body_markdown":"Skip this section if you do not use the StoreConnect POS.\n\nThe guide assumes you are on the latest v21 website release. Changes that already shipped in v21 patch releases are not repeated here.\n\nEach heading starts with a label. **Action required** means you must do something, or the heading tells you in its first sentence who must. **Review** means check whether the change affects your implementation.\n\n## Before you upgrade\n\nUse this list to find the subsections that need your attention:\n\n- A device has a custom layout chosen in POS **Settings \u003e Layouts**: see [Custom layouts are no longer chosen in POS Settings](#action-required-custom-layouts-are-no-longer-chosen-in-pos-settings).\n- You have store variables whose key begins with `pos.`: see [Store variables with a pos prefix sync to every device](#action-required-store-variables-with-a-pos-prefix-sync-to-every-device).\n- You use Latitude at more than one outlet: see the Payments section of this guide.\n- You have custom layout pages or record lists: see [Layout page titles are hidden by default](#review-layout-page-titles-are-hidden-by-default).\n- Every POS store: add the new **Shipment** status values after the package upgrade, described in [Shipment status values](#action-required-shipment-status-values).\n\n## Salesforce package changes\n\n### Review: Fulfillment station objects\n\nFive new objects route POS orders to physical preparation areas such as a kitchen or bar. Permissions are included in the StoreConnect Administrator, Order Manager, and Sync permission sets, and the package adds tabs for **Fulfillment Station** and **Fulfillment Category**.\n\n| Object | Purpose |\n|---|---|\n| `Fulfillment_Station__c` | A preparation area at an outlet, with a status, offline behavior, routing weight, fallback station, parent station, and a configurable status flow |\n| `Fulfillment_Category__c` | A named routing bucket, such as Hot Kitchen or Drinks |\n| `Product_Fulfillment_Category__c` | Links a product to the categories it belongs to |\n| `Fulfillment_Category_Station__c` | Maps a category to the station that handles it, per outlet |\n| `Register_Fulfillment_Category_Station__c` | A per-register override of the outlet mapping |\n\n`Shipment__c` gains `Fulfillment_Station_Id__c` and print tracking fields (`Auto_Printed_At__c`, `First_Printed_At__c`, `First_Printed_Register_Id__c`, `Last_Printed_At__c`, `Last_Printed_Register_Id__c`) for docket printing.\n\nWith no records configured, nothing routes and the POS is unchanged. The station screens are not shown on a device until an administrator adds them to a custom sidebar or home quick action group.\n\n1. If you use only the standard StoreConnect permission sets, no action is needed.\n2. If you have custom permission sets, navigate to **Setup \u003e Permission Sets** and assess whether access to the five objects is appropriate for that set's audience.\n\nSee [Configure POS fulfillment station routing](pos-fulfillment-station-routing) and [Fulfillment Station Object Reference](fulfillment-station-object-reference).\n\n### Action required: Shipment status values\n\nThe **Status** picklist on `Shipment__c` gains `preparing`, `on_loan`, `returned`, and `cancelled`. The POS writes `preparing` for takeaway orders at a fulfillment station and `on_loan` and `returned` for rental fulfillments. A package upgrade does not add picklist values to an existing field, and a missing value makes those updates fail to sync.\n\n1. After the package upgrade, open the **StoreConnect Console**, select **Setup**, then **Manage picklist values**, and apply the new values for **Shipment**.\n2. If rental fulfillments raised sync errors before the upgrade, retry them from the [sync error tool](sync-error-tool).\n\nThe same tool adds the other new picklist values listed in the E-commerce section. See [Managing StoreConnect upgrades](managing-storeconnect-upgrades).\n\n### Review: Products hidden from the POS\n\nA product whose **Excluded Channels** field on **Product** includes `pos` is no longer synced to POS devices. Blank, the default, keeps every product available, so nothing changes until you set the field. A product already on an order stays visible for returns.\n\n### Review: Promotions limited to a channel\n\nAn Advanced promotion whose new **Channels** field on `Promotion2__c` is set to `web` no longer applies at the POS, and one set to `pos` applies only there. Blank applies everywhere, so existing promotions are unchanged. The POS also skips lines excluded from price reductions and lines already on sale, as the web store does.\n\n### Review: Outlet, delivery, notes, and discount fields on orders\n\nThese new fields are populated by the POS and need no configuration. Review whether they should appear on your page layouts or reports.\n\n| Object | New field | Purpose |\n|---|---|---|\n| `Order` | `Outlet_Id__c` | The outlet that created the order |\n| `Order` | `Memo__c` | A short note, up to 80 characters, used to identify open orders such as a table number |\n| `Cart_Fulfillment__c`, `Shipment__c` | `Requested_Delivery_Date__c`, `Shipping_Notes__c` | Captured in the arrange-delivery flow and copied to the shipment when the order completes |\n| `Cart_Item__c`, `OrderItem` | `Notes__c` | Per-item notes, when cart item notes are enabled |\n| `OrderItem` | `Unit_Manual_Discount__c` | The per-unit discount an operator applied by hand |\n| `Cart_Item__c`, `OrderItem` | `Unit_Discount_Points__c` | Per-unit points discount |\n| `OrderItem` | `Total_Points_Amount__c` | Points used for the whole line |\n| `Order`, `OrderItem` | `Cancellation_Reason__c`, `Cancelled_At__c`, `Cancelled_By_User_Id__c` | Who canceled the order or line, when, and why |\n\nThe full field tables are in the E-commerce section of this guide.\n\n## Behavior changes\n\n### Action required: Custom layouts are no longer chosen in POS Settings\n\nThis applies only if any device had a custom layout chosen in POS **Settings \u003e Layouts**. That section has been removed, together with the per-device selection it stored. A custom layout now replaces a built-in view when the layout's **Identifier** on the `Pos_Layout__c` record matches the built-in name.\n\n| Built-in view | Identifier to set |\n|---|---|\n| Order list | `order_list` |\n| Order record | `order_record` |\n| Order summary after checkout | `order_summary_record` |\n| Customer list | `customer_list` |\n\n1. List the custom layouts that were chosen on your devices before upgrading.\n2. On each `Pos_Layout__c` record, set **Identifier** to the built-in name it should replace. Leave the identifier of a layout that is opened from a menu or quick action unchanged.\n3. After the next sync, open the view on a device and confirm the custom layout is shown.\n\nSee [POS layouts](pos-layouts).\n\n### Action required: Store variables with a pos prefix sync to every device\n\nEvery store variable whose key begins with `pos.` is now copied to the offline database on every register for the store, so the POS can read its settings without a connection. Any value stored under a `pos.` key is readable on the device.\n\n1. Review your `Store_Variable__c` records whose **Key** begins with `pos.`.\n2. Move any secret, such as an API key or password, out of the `pos.` namespace. Payment provider credentials belong in the payment provider's API options, not in a store variable.\n\n### Review: Payment method settings are read from synced store variables\n\nThe POS reads which payment methods are enabled, their labels, and whether a purchase order number is required from the `pos.payment_options.\u003ctype\u003e.*` store variables synced to the device, rather than from a settings call at startup. A change to one of these variables takes effect once the next POS sync completes, not on a page refresh. The same variables now drive which methods appear on the end-of-shift screen.\n\nA register can also switch cash on or off locally using the `settings:update` action, unless `pos.payment_options.cash.enabled` is set for the store, in which case the store value applies to every register and the local setting is ignored.\n\nSee [POS payment methods](pos-payment-methods).\n\n### Review: Layout page titles are hidden by default\n\nLayout-driven pages, which are record lists, grids, and custom pages, no longer show a title row when the title is the only thing in it. A title beside **Back**, a title tag, or a secondary action still shows. Fulfillment station screens always keep their title. To keep title-only rows, set the `pos.layouts.always_show_title` store variable to `true`.\n\n### Review: Navigation sidebar and topbar\n\nThe sidebar and topbar are redrawn. Actions that do not fit move into a **More** menu, the sidebar expands over the page instead of pushing it aside, and action groups open as menus. The bars still show the action groups with the identifiers `sidebar_main_menu`, `sidebar_fixed_menu`, `topbar_main_menu`, `session_start_actions`, and `session_actions`, ordered by **Position**, so existing custom navigation carries over. Give your most important actions the lowest positions, because the highest positions move into **More** first. On screens narrower than 1024 pixels the cart collapses to a card and opens with a button or a swipe. See [POS action groups](pos-action-groups).\n\n### Review: Customer email is optional at the POS\n\nA POS customer can be saved with only a phone number, and an operator can clear an email already on file. Web registration still requires an email. If you have Flows that assume every contact created at the POS has an email, review them.\n\n### Review: Records can be searched on the server first\n\nFor an object that has a sync scope, the POS searches the records on the device first and offers **Search all records**. Setting `pos.remote_records.\u003cobject\u003e.remote_first` to `true` sends searches of that object to the server straight away. Unset, nothing changes.\n\n### Review: Unsaved changes are visible, and block returns until saved\n\nAnything an operator does is saved on the device first and sent to the server in the background. A new **Unsaved Changes** view, opened from **Settings \u003e Manage Data**, lists each change still waiting to be saved with its status, so a stuck change can be retried or discarded instead of sitting silently on the device. A return or exchange cannot be started on an order that still has unsaved changes. Shifts that fail to sync are tracked by the same engine.\n\n### Review: POS Liquid prices use the outlet price book\n\nThis applies only if an outlet has its own **Pricebook** set on the `Outlet__c` record. Prices rendered by POS Liquid templates, such as product cards and custom views, now resolve through the same account, membership, outlet, and store hierarchy the cart uses, so a displayed price matches the price charged.\n\n### Review: Expired account credits are excluded from the available total\n\nThe account credit balance offered at checkout no longer includes credits past their expiry date, and the account credit form applies the same rule. A customer with expired credits sees a lower available total than before. No configuration is needed.\n\n### Review: Deposit sales charge only the amount due now\n\nOn a deposit or part-payment sale, the manually keyed card and pay on account forms now default to the amount due now, matching bank transfer and cheque. The order balance remains the cap for a keyed amount, so an operator can still take more than the deposit up to the balance.\n\n### Review: Promotion display in the cart, totals, and returns\n\nA discounted line shows its price in red with the full price as `Was $X`, followed by one row for each promotion that discounted it and a **Price adjustment** row for the operator's own discount. Cart-wide promotions are named once in the totals, after a **Subtotal** row. Free products appear as their own `$0` line naming the promotion. The return screen and refund cart show the promotion share belonging to the units being returned. If you have a custom cart or receipt view that lays out promotion lines, review it against the new structure.\n\n### Review: Refund options\n\nThree refund changes affect operators:\n\n- **Overpayment refunds.** When the amount paid exceeds the order total, the primary action on the order becomes **Give refund**, which refunds the difference without selecting items.\n- **Refund to a terminal not used on the order.** Linkly and Quest Airpay TAP can be offered as a refund destination even when the original payment was cash or another method. This is off until you add `\"refund_without_original_payment\": true` to the provider's **API Options**. Tyro refunds can be sent through the terminal from the returns flow.\n- **Cash drawer.** Processing a refund opens the cash drawer when any of the refund's payment methods is configured as a drawer trigger, the same rule as taking a payment.\n\nSee [Returns, refunds, and exchanges at POS](returns-refund-exchange-at-pos).\n\n### Review: Quest Airpay TAP transactions are recorded on the server\n\nPurchases, refunds, and recovered purchases made on a Quest Airpay TAP terminal are now recorded on the server as they happen and linked to the payment when the order is submitted, so an interrupted sale can be recovered at the next checkout instead of charging again. No configuration is needed.\n\n### Review: Linkly and Latitude payments recover after an interruption\n\nA Linkly or Latitude payment or refund that was interrupted, for example by a page reload, is found when the next payment or refund starts, and the operator can wait for it or cancel it instead of charging twice. No configuration is needed. See [Linkly payment provider](linkly-payment-provider).\n\n### Review: Subscription products capture a card at the POS\n\nA cart containing a subscription or save-card product requires an identified customer, and the POS captures the customer's card for later billing through a paired Square Terminal or a web page. Preauthorize-only products cannot be sold at the POS. See the Subscriptions section of this guide.\n\n## Optional features controlled by store variables\n\nThese are off until you set the variable, so nothing changes for existing registers. Changes take effect after the next store sync.\n\n| Store variable | What it enables |\n|---|---|\n| `pos.open_orders.enabled` | Open orders: convert a cart to an order that stays open for table service or a tab, with a **Memo** column in the order list labeled by `pos.memo.label` |\n| `pos.fast_checkout.enabled` | A one-tap **Charge** path for cash and integrated card terminals. Opt a single tender out with `pos.payment_options.\u003ctype\u003e.fast_checkout_enabled` set to `false` |\n| `pos.smart_cash.enabled`, `pos.smart_cash.arrangement` | Suggested cash amounts on the cash tender screen |\n| `pos.tipping.enabled`, `pos.tipping.percentages`, `pos.tipping.amounts`, `pos.tipping.amount_threshold`, `pos.tipping.tax_basis`, `pos.tipping.cash_prompt` | A tip prompt after payment, per payment. Not offered on integrated card terminals |\n| `pos.cart_item_notes.enabled`, `pos.cart_item_notes.label` | A notes field on each cart item, captured to the order item |\n| `pos.checkout.save_incomplete_order` | A **Save \u0026 exit** action that converts a plain cart into an incomplete order |\n| `pos.list_views.load_initial_records` set to `false` | List views open empty and load records only when the operator searches or filters. Useful on large catalogs |\n| `pos.stock.display` | How stock is shown on product surfaces: `exact`, `indicative` (the default, and the previous behavior), or `off` |\n| `pos_domain` | One extra hostname at which the store answers, so POS devices can stay on their installed address while the storefront moves to a new domain. A bare hostname, not a URL. Inert while it equals the store's own domain. Set it before changing the store domain |\n| `pos.layouts.always_show_title` | Keeps the title row on layout pages when the title is the only thing in it |\n| `pos.remote_records.\u003cobject\u003e.remote_first` | Searches of that object go to the server straight away instead of the device first |\n\n## Layout and template changes\n\n### Review: New layouts, actions, and drops\n\nNo changes to existing layouts are required. Review these if you maintain custom POS layouts, views, or print templates:\n\n- An `order_summary_record` layout overrides the summary shown after checkout, with the checkout outcome message available as `{{ outcome_message }}` in a custom view.\n- Quick search layouts support filters, field overrides, and a custom view with a row-click action. List layouts gain a records toolbar for filters, sort, and layout controls.\n- Fulfillment station layouts render station tickets and the operations view.\n- Cart custom fields can be copied to the order with the `custom_field_copy` layout setting.\n- New actions include `cart:new`, `cart:new_open_order`, `open_order:submit`, `open_order:update`, `order:cancel`, `order:resume`, `checkout:refund`, `cart_item:cancel`, `record:save`, `settings:update`, `sync:clear`, `nav:station_fulfillments`, and the `unsaved_changes:*` actions.\n- Order item drops expose `notes`, order drops expose `refund_orders`, fulfillment drops are available, and order custom data is readable in Liquid. Map and list objects are supported in POS Liquid.\n- Quantity fields are typeable, and the cash keypad opens showing the amount due.\n\nSee [POS layouts](pos-layouts) and [POS print templates](pos-print-templates)."}