Switchboard DocsPolaris-native Shopify Functions

Pickup Location Reorder

Reorders pickup-location options at checkout. Powered by Shopify's cart.delivery-options.transform.run Function target.

Three modes — pick the one that fits your situation. Modes are per-rule; you can have multiple rules with different modes and priorities running side-by-side.


Modes at a glance

Mode Configuration required When it fires Best for
Fixed order Admin: pick + order your locations Always Flagship-store pinning, region-specific stores you want emphasized
Auto-match None — zero config Logged-in customers with saved default address Multi-location retailers who already have a store in each major region
By cart attribute Admin: pick match style. Storefront: write to _switchboard_pickup. Whenever your storefront sets the attribute Custom logic — last-order pickup, B2B segments, ML-scored recommendations

Fixed order

Pin a specific order of pickup locations for everyone. The merchant uses Up/Down/Remove buttons in the admin to build an ordered list. At checkout, those locations are sorted to the top in that exact order; locations not in the list stay where Shopify puts them.

When to use: you have a "we want THIS store first, everywhere" preference. Common for flagship stores, regional headquarters, or stores you're trying to promote.

Configuration:

  1. Admin → New rule → mode Fixed order.
  2. Add locations from the dropdown. Drag-equivalent — use ↑ Up / ↓ Down to reorder. Remove any you don't want.
  3. Save with status Active.

No storefront code required.

Examples

  • One pin: Add only "My Custom Location" to the list. That location appears at slot 0 for every cart, every customer.
  • Top three: Add three locations in your preferred order. The other locations slot in after them in Shopify's default order.
  • Full re-sort: Add all your pickup-enabled locations in the order you want. Every customer sees identical ordering.

Auto-match

Reads the logged-in customer's saved default address. Pins the pickup location whose own address matches by province (preferred) or country (fallback). Zero per-rule configuration — Switchboard fetches each pickup location's address from your Shopify Locations settings and computes the mapping automatically.

When to use: you have a store in each major region you serve, and you want the closest one to surface first for repeat / logged-in customers.

Configuration:

  1. Admin → New rule → mode Auto — match logged-in customer's saved address.
  2. Save with status Active.

That's it. The admin shows a live preview of the auto-mapping based on your pickup locations' actual addresses:

Customer in MB → pin Winnipeg Customer in NB → pin New Brunswick Customer in ON → pin My Custom Location Customer in QC → pin Quebec

The mapping refreshes every time the merchant saves any rule (in case pickup locations get added or moved).

No storefront code required.

When it fires (and when it doesn't)

Customer state Geo fires?
Logged in, has saved default address ✅ Yes — Shopify auto-fills the address
Logged in, no saved address ❌ No (no signal) — rule silently steps aside
Anonymous guest ❌ No (no signal) — rule silently steps aside

When the rule doesn't fire, lower-priority rules still get a chance. So you can stack:

  • Priority 50: Auto-match (fires for logged-in customers)
  • Priority 100: Fixed-order fallback (fires for everyone else)

The first one that applies per delivery group wins.


By cart attribute

Reads the cart attribute _switchboard_pickup. Its value can be:

  • A plain title string — pins that one pickup location.
  • A JSON-encoded array of titles — sorts those locations to the top in the order given.

Plus a match style chosen in the admin:

  • Exact name — the value must equal a pickup-location title (case-insensitive).
  • Substring (region keyword) — the value just needs to appear inside a title.

When to use

  • Last-order pickup routing (Liquid reads customer.last_order.line_items[].fulfillment.location.name)
  • B2B segments (Liquid checks customer.tags, picks a different ordered list)
  • Storefront-side logic too custom for the other two modes (custom CRM data, A/B-tested ordering, etc.)

Configuration

Admin:

  1. New rule → mode By cart attribute → match style Exact name (recommended) or Substring (region keyword) → status Active. Save.

Storefront — easiest path:

// One location
Switchboard.setPickup("Quebec");

// Ordered list
Switchboard.setPickup(["Quebec", "Winnipeg", "New Brunswick"]);

// Clear
Switchboard.clearPickup();

Storefront — no SDK:

fetch('/cart/update.js', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    attributes: { _switchboard_pickup: 'Quebec' }
    // or: { _switchboard_pickup: JSON.stringify(["Quebec","Winnipeg"]) }
  })
});

See /docs/sdk for the full SDK reference and /docs/liquid for Liquid recipes.

Match-style: exact vs substring

Value sent Pickup location titles Exact mode Substring mode
"Quebec" ["Quebec", "Winnipeg"] ✅ Pins Quebec ✅ Pins Quebec
"Quebec" ["Quebec Warehouse", "Toronto"] ❌ No match ✅ Pins Quebec Warehouse
"TX" ["Houston, TX", "New York"] ❌ No match ✅ Pins Houston, TX
"Dallas" ["Dallas, TX Hub", "Houston, TX"] ❌ No match ✅ Pins Dallas, TX Hub

Rule of thumb: if you send a full literal title, use Exact. If you send a region code / city / keyword that's a substring of the title, use Substring.

Ordered arrays

Send an array → those titles get sorted to the top in the order given. Titles not found in the cart's pickup options are silently skipped (no gaps left at the top).

Cart attribute value Pickup options (before) Result (after)
["Quebec","Winnipeg"] [A, B, Quebec, Winnipeg] [Quebec, Winnipeg, A, B]
["Missing","Quebec","Also"] [A, B, Quebec] [Quebec, A, B] (missing items skipped)
[] (empty array) unchanged unchanged — rule doesn't apply, fallback rules get a turn

Priority across rules

Each rule has a numeric priority (default 100, lower wins). Per delivery group, the first applying rule wins. Once a rule applies, no further rules run for that group.

Typical setup:

  • Priority 50 → Auto-match rule (fires for logged-in customers)
  • Priority 100 → "By cart attribute" rule (fires when storefront writes the hint)
  • Priority 150 → Fixed-order fallback (always applies — catches everyone else)

The customer sees ONE rule's output. Picking sensible priorities prevents conflicts.


Fail-safe behavior

The function NEVER throws and NEVER blocks checkout. On any failure (malformed cart attribute JSON, missing pickup options, mismatched titles, etc.), the rule silently emits zero operations and Shopify's default pickup order is shown.

Failure Result
Malformed cart attribute JSON Treated as plain title; if no match, no-op
Cart attribute set to a non-string, non-array value (e.g. object) Rule does not apply
Title in array doesn't match any pickup option That entry skipped; tight stack at top
All titles in array miss No moves emitted
Customer not logged in (Auto-match) Rule does not apply; lower-priority rules get a turn
No pickup locations enabled in the shop No moves emitted (function has no targets)

How it actually works (technical)

When the merchant saves a rule, Switchboard:

  1. Persists the rule to Postgres.
  2. Pushes the merchant's pickup locations + their addresses to a Shopify metafield ($app:switchboard.pickup_locations) so the function can read them for Auto-match.
  3. Pushes the rules list to a Shopify metafield ($app:switchboard.rules).

At checkout, the function reads both metafields, plus the cart's _switchboard_pickup attribute. It emits deliveryOptionMove operations to reorder the pickup picker.

See extensions/switchboard-delivery-reorder/src/ in the repo for the function code.


Constraints (Shopify-imposed)

  • deliveryOptionMove is the only operation type for this function target. You can move existing options around; you can't add or hide them.
  • cart.deliveryGroups[].deliveryAddress is null for anonymous carts without a delivery address. Auto-match silently no-ops in that case.
  • No cart.token — pickup options expose handle and title only. Switchboard matches on the numeric location ID embedded in the handle, with a title-snapshot fallback.

See SHOPIFY_APP_PLAYBOOK.md in the repo for the full list of Shopify Function constraints we hit while building this.