Pickup in themes

Delivery or pickup at a store: the shopping ctx, pickup locations and stock, the shopping_mode_set action, and how Canvas builds it.

A shopper shops in one of two modes: delivery, or pickup at one of the store's locations. In pickup mode the platform scopes every stock figure to that location — product and variant availability, product cards, filters, search and the cart — and checkout skips shipping and shows a pickup card. A theme shows the mode, lets the shopper switch, and explains what is or is not at the chosen store. It never works out stock itself.

The no-pickup rule#

Context#

Add shopping to the ctx_needs of every section that reads it. shopping is available on every page; shop.pickup_locations comes with shop, the cart fields with cart, and variant stock with product.

Field

What it holds

shopping.mode

delivery or pickup.

shopping.pickup_location

The chosen pickup location in pickup mode; null for delivery.

shopping.pickup_available

True when at least one location offers pickup to this shopper's market. Gate every pickup surface on it.

shop.pickup_locations

The locations offered to the shopper, in the store's fulfillment order. Each has id, name, address_lines (laid out the way its country writes addresses, ending with the country), city, country_code, ready_in (1_hour, 2_hours, 4_hours, 24_hours, 2_4_days), ready_in_label (for example "Usually ready in 2 hours", in the shopper's language), instructions and hours (null when not set) and selected.

cart.mode, cart.pickup_location

The cart's mode and the location it is collected from. A line the location has none of has available_for_sale false and availability_reason not_at_pickup_location; a line it has some but not enough of keeps the normal insufficient-quantity reason. Show availability_reason_label either way rather than mapping the codes yourself.

variant.pickup_availability

On each entry of product.variants: one row per offered location with location_id, name and status (in_stock, low_stock, out_of_stock). Empty when the store offers no pickup to the shopper. A simple product carries it on its single variant.

vascula
{%- assign pk_variant = nil -%}
{%- assign pk_id = product.selected_variant.id -%}
{%- if pk_id == nil and product.options.size == 0 -%}{%- assign pk_id = product.variants.first.id -%}{%- endif -%}
{%- for v in product.variants -%}{%- if v.id == pk_id -%}{%- assign pk_variant = v -%}{%- break -%}{%- endif -%}{%- endfor -%}

Switching mode: shopping_mode_set#

Post mode (delivery or pickup) and, for pickup, location_id from shop.pickup_locations. Build the form with {% form 'shopping_mode_set' %}: it posts to /shopping-mode with the CSRF token, so it works without JavaScript and returns to the page with a 303. A location outside the shopper's market is refused with STOREFRONT_PICKUP_LOCATION_UNAVAILABLE and the mode stays as it was. The choice is kept in a cookie, and a later change of country back to a market that does not offer the location resets the shopper to delivery.

vascula
{% form 'shopping_mode_set' %}
  <button type="submit" name="mode" value="delivery">Delivery</button>
{% endform %}
{% form 'shopping_mode_set' %}
  <input type="hidden" name="mode" value="pickup">
  {%- for l in shop.pickup_locations -%}
    <button type="submit" name="location_id" value="{{ l.id }}"{% if l.selected %} aria-current="true"{% endif %}>
      {{ l.name }} — {{ l.address_lines | join: ', ' }} · {{ l.ready_in_label }}
    </button>
  {%- endfor -%}
{% endform %}

The storefront bridge submits the form in the background and its JSON response has no redirect, so the page stays put. Every stock figure on the page depends on the mode, so reload once the bridge reports success:

javascript
document.addEventListener("tf:action-success", function (event) {
  if (event.detail.action === "shopping_mode_set") window.location.reload();
});

What to show#

Surface

Delivery mode

Pickup mode

Header

A Delivery chip that opens the store list.

Pickup · store name; the same list with the store marked.

Product page

"Also available for pickup at N stores" from the rows whose status is not out_of_stock.

In stock there: "Pickup at store · ready_in_label". Not stocked there: "Not at store", the other stores with stock (one-tap shopping_mode_set each) and a way back to delivery.

Product cards

Unchanged.

An out-of-stock card is not at this store: say so instead of "Sold out".

Cart

Unchanged.

"Picking up at store · ready time" with a way to change; lines with not_at_pickup_location offer another store or delivery; no shipping estimate.

Decide only from what the platform reports: available_for_sale, inventory_status, the pickup_availability rows and availability_reason. Do not compute stock, count units or guess which stores carry an item.

Related: Hosted Actions for the bridge events, and CTX for the full field reference.