Drop-in theme.liquid snippets that drive Switchboard from Shopify's customer Liquid object. Every recipe assumes the Switchboard SDK app embed is enabled (see /docs/sdk for how).
These snippets are starter material. Adapt to your theme, your section structure, and your customer-segmentation strategy. They're written to be readable, not minified.
Add to theme.liquid (the main layout), inside a section that loads on every page, or in a snippet that you {% render %} from theme.liquid. The key requirement is that the snippet runs on every page the customer visits, so the cart attributes are set well before they reach checkout.
If a snippet only runs on a specific template (e.g. product.liquid), the attributes may not be set when the customer visits the cart from a different page. Avoid that.
customer exposesWhen the customer is logged in, the customer Liquid object is available everywhere. Useful fields:
{{ customer.id }}
{{ customer.email }}
{{ customer.first_name }}{{ customer.last_name }}
{{ customer.orders_count }}
{{ customer.total_spent }} {# in cents #}
{{ customer.tags }} {# comma-separated list #}
{{ customer.default_address.first_name }}
{{ customer.default_address.last_name }}
{{ customer.default_address.address1 }}
{{ customer.default_address.city }}
{{ customer.default_address.province_code }} {# "ON", "QC", etc. #}
{{ customer.default_address.country_code }} {# "CA", "US", etc. #}
{{ customer.default_address.zip }}
{{ customer.addresses | size }} {# all saved addresses #}
{{ customer.last_order.created_at }}
{{ customer.last_order.shipping_address.province_code }}
For pickup-history routing, you can iterate customer.orders and read order.line_items[].fulfillment.location.name. Be aware Shopify limits customer.orders to a recent slice — typically the last 50.
Pairs with Pickup Reorder → Auto-match mode if your location titles match province codes, or By cart attribute (Substring) mode if your titles only contain the province as a substring.
{%- if customer and customer.default_address.province_code -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setPickup({{ customer.default_address.province_code | json }});
}
});
</script>
{%- endif -%}
If you offered the customer pickup last time and they used it, surface that same location first next time.
Pairs with Pickup Reorder → By cart attribute (Exact name) mode.
{%- if customer.last_order -%}
{%- for line in customer.last_order.line_items -%}
{%- if line.fulfillment.location.name -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setPickup({{ line.fulfillment.location.name | json }});
}
});
</script>
{%- break -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
Different customer tags → different pickup orderings.
Pairs with Pickup Reorder → By cart attribute (Exact name) mode.
{%- if customer.tags contains 'wholesale' -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setPickup(['Wholesale Hub', 'Main Store']);
}
});
</script>
{%- elsif customer.tags contains 'vip' -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setPickup(['VIP Lounge', 'Main Store']);
}
});
</script>
{%- endif -%}
Used by validation rules that gate by country, region, etc.
{%- if customer and customer.default_address.country_code -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setCartAttribute('country', {{ customer.default_address.country_code | json }});
}
});
</script>
{%- endif -%}
{%- if customer -%}
{%- assign tier = 'standard' -%}
{%- if customer.tags contains 'wholesale' -%}{%- assign tier = 'wholesale' -%}{%- endif -%}
{%- if customer.tags contains 'vip' -%}{%- assign tier = 'vip' -%}{%- endif -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setCartAttribute('loyalty_tier', {{ tier | json }});
}
});
</script>
{%- endif -%}
One cart write instead of three.
{%- if customer -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (window.Switchboard) {
Switchboard.setBatch({
country: {{ customer.default_address.country_code | json }},
orders_count: {{ customer.orders_count | json }},
total_spent_cents: {{ customer.total_spent | json }}
});
}
});
</script>
{%- endif -%}
In the cart section template:
<label>
<input type="checkbox" data-switchboard-attr="age_confirmed" value="true">
I confirm I am 18+ years of age.
</label>
The SDK's declarative mode syncs the checkbox state into the _switchboard.age_confirmed cart attribute on every change. Pair with a validation rule age_confirmed is not equals true to block checkout when unchecked.
Pairs with Cart Transform. Set minPercentOfVariant to e.g. 0.7 in the rule's advanced settings so the discount never drops below 70% of the variant price.
{%- if customer.tags contains 'vip' -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!window.Switchboard) return;
fetch('/cart.js').then(function (r) { return r.json(); }).then(function (cart) {
cart.items.forEach(function (line) {
var discounted = Math.round(line.price * 0.75); // 25% off
Switchboard.signAndSetPrice(line.key, discounted);
});
});
});
</script>
{%- endif -%}
Add to a section that renders on the cart page. Replace protection_variant_id with the variant ID of your hidden shipping-protection product.
<label>
<input type="checkbox" id="ship-protect"> Add shipping protection ($2.99)
</label>
<script>
document.getElementById('ship-protect').addEventListener('change', function (e) {
if (!window.Switchboard) return;
if (!e.target.checked) return;
Switchboard.addLineItemWithProperties({{ section.settings.protection_variant_id }}, 1, {})
.then(function (cart) {
var line = cart.items[cart.items.length - 1];
return Switchboard.signAndSetPrice(line.key, 299);
});
});
</script>
Most production stores combine all three features. A typical theme.liquid snippet looks like:
{%- if customer -%}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!window.Switchboard) return;
// 1. Tag the cart with everything Validation rules might reference
Switchboard.setBatch({
country: {{ customer.default_address.country_code | json }},
loyalty_tier: {%- if customer.tags contains 'vip' -%}"vip"{%- elsif customer.tags contains 'wholesale' -%}"wholesale"{%- else -%}"standard"{%- endif -%},
orders_count: {{ customer.orders_count | json }}
});
// 2. Hint Pickup Reorder with the customer's last-used pickup spot (if any)
{%- if customer.last_order -%}
{%- for line in customer.last_order.line_items -%}
{%- if line.fulfillment.location.name -%}
Switchboard.setPickup({{ line.fulfillment.location.name | json }});
{%- break -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
});
</script>
{%- endif -%}
That single block handles validation tagging + pickup hinting in one page load. Cart Transform usually lives in its own checkbox / button handlers per recipe.
customer.orders typically returns the most-recent 50. For deeper history use the Storefront API from your storefront JS.fulfillment on unfulfilled orders. The "last pickup location" recipe only works after the previous order was actually fulfilled.customer is set (i.e. the customer is logged in). Wrap in {%- if customer -%} guards.