How To Personalize Shopify Emails With Liquid And Safe Fallbacks
Shopify Liquid lets you pull real order and customer details into your store’s emails, so messages feel personal without manual work. The trick is choosing variables that exist in the specific context, like using shipping and billing fields in notification templates, and customer fields when they’re actually available. Just as important, add safe fallbacks so missing data doesn’t create awkward greetings or blank subject lines, using the default filter for simple cases and if/else logic when you need tighter control. One small formatting choice, like where you place commas, spaces, or exclamation points, is often the difference between polished personalization and an email that quietly looks broken.
Liquid personalization in Shopify Email templates: what works where
Shopify Email vs notification emails
Shopify has two very different “email worlds,” and Liquid behaves differently in each.
Shopify Email (Shopify Messaging) is for marketing campaigns and automations you send to subscribers. You can add Liquid in a Custom Liquid section or by choosing “Code your own,” but you only have access to Shopify Email’s supported variables (a curated set, not the full universe of order data).
Notification emails are transactional messages like order confirmation, shipping confirmation, and refund notifications. These live in your admin under Settings > Notifications, and Shopify provides a large Liquid variable set designed specifically for each notification type. Shopify also notes that variables can exist but still be “not applicable” if the data isn’t available at send time.
One practical “gotcha”: in notification templates, order properties often aren’t referenced as order.something. For example, Shopify’s docs show {{ shipping_method.title }} rather than {{ order.shipping_method.title }} in order emails.
Where Liquid can be added safely
In Shopify Email, Liquid is safest in the places Shopify explicitly designed for it:
- A Custom Liquid section in the email editor (Liquid + HTML, with a size limit).
- A fully custom-coded email via “Code your own” (for stores that have access).
If you’re using custom Liquid in Shopify Email, plan your footer early. Shopify requires unsubscribe_link or unsubscribe_url, and if open tracking is enabled, the open tracking variable is required too.
For notification emails, the “safe” approach is the opposite: stay inside the notification template’s documented variables and patterns, and assume some fields will occasionally be missing depending on timing (payment, fulfillment, delivery, and so on).
Customer and order fields you can use in Shopify emails
Customer name, email, and tags
For most personalization, start with the customer object. In Shopify Email, the safest “high-impact” fields are the basics:
customer.first_name,customer.last_name, andcustomer.namefor greetings and subject lines.customer.emailwhen you need to confirm which inbox you’re writing to (use sparingly in visible copy).customer.tagsfor simple segmentation logic, like VIP copy or wholesale messaging.
A practical pattern is to treat the name as optional and build your greeting around it. Even customers with accounts can have missing first or last names, especially if the list was imported or collected through a popup.
Order, shipping, and line item details
Order personalization depends heavily on the email type.
In notification emails (order confirmation, shipping confirmation, etc.), Shopify exposes order properties directly in the template context. That includes fields like shipping_address.*, shipping_method.title, line_items, item_count, order_status_url, and even the order’s tags array.
In Shopify Email, order-level fields aren’t always available unless you’re in an automation that provides them (for example, abandoned checkout emails can include a checkout object with line items and totals). Keep your marketing emails customer-first, and only lean on cart or checkout objects when you know the template supports them.
Marketing consent and locale fields
For consent-based personalization, Shopify Email supports customer.accepts_marketing, which is useful for showing extra promotional blocks only to opted-in subscribers.
For language and locale, abandoned checkout templates can include customer_locale (like en or en-CA). That gives you a clean way to swap small copy blocks without maintaining separate templates for every language.
Safe fallbacks when Shopify email variables are missing
Default greetings and generic copy
Even “simple” fields like a first name can be empty. And in Shopify Email, some objects just will not exist in certain campaigns. Safe fallbacks keep your copy polished in every send.
The easiest win is a greeting that degrades gracefully:
{% assign first_name = customer.first_name | strip %}
{% if first_name != blank %}
Hi {{ first_name }},
{% else %}
Hi there,
{% endif %}
For body copy, write sentences that still read naturally without personalization. Instead of “We picked this just for you, {{ customer.first_name }},” use “We picked a few options you might like,” and let the personalized parts live in optional add-ons.
When you do want “default text,” Liquid’s default filter is a clean option:
Hi {{ customer.first_name | default: "there" }},
Use this for short substitutions. For anything with punctuation or spacing that might look weird, an explicit if/else reads better.
Using if, elsif, else, and blank checks
In Shopify’s Liquid, blank is your friend. It catches empty strings and nil values, so you can use one check for most missing-variable issues.
A common pattern is a tiered fallback:
{% assign first_name = customer.first_name | strip %}
{% assign full_name = customer.name | strip %}
{% if first_name != blank %}
Hi {{ first_name }},
{% elsif full_name != blank %}
Hi {{ full_name }},
{% else %}
Hi there,
{% endif %}
For tags, avoid assuming the tag exists. Check it:
{% if customer.tags contains "VIP" %}
VIP early access is live now.
{% else %}
New arrivals are live now.
{% endif %}
One more practical tip: keep “blank checks” close to where the variable is used. It makes future edits safer, especially when you or a teammate revises a section weeks later.
Handling missing nested objects
Nested objects are where emails quietly break. A customer might exist, but the address might not. A shipping address might exist, but province might be blank. And in Shopify Email, order objects might not exist at all unless the automation provides them.
The safest approach is to check the parent object first, then check the child fields:
{% if shipping_address and shipping_address.city != blank %}
Delivering to {{ shipping_address.city }}.
{% endif %}
When you need to show a whole block (like a shipping summary), wrap the entire block in a single guard clause. That prevents half-rendered sections with hanging labels like “Ship to:” and nothing after it.
Preventing null chains in dot notation
Avoid long chains like this when you are not 100% sure the object exists:
{{ order.shipping_address.province }}
If order is missing, that chain can fail. Instead, split it and guard each level:
{% if order and order.shipping_address %}
{{ order.shipping_address.province | default: "" }}
{% endif %}
In notification templates, you may not even have order as the root object, so the same rule applies: check the actual parent object Shopify gives you in that template (like shipping_address) before you reach for deeper fields. This one habit prevents most “Rendered template is invalid” surprises when you test with different customer and order scenarios.
Conditional content blocks for segments, tags, and behaviors
Showing VIP or wholesale messaging
Customer tags are the simplest way to control conditional content in Shopify emails. If you already tag VIPs, wholesalers, or loyalty tiers in Shopify, you can swap headlines, offers, and CTAs without maintaining separate templates.
A clean VIP example:
{% if customer and customer.tags contains "VIP" %}
<p><strong>VIP early access:</strong> Shop the new drop before it’s public.</p>
{% else %}
<p>New arrivals are live. Take a look at what’s new.</p>
{% endif %}
For wholesale, keep the message practical. Mention terms like “log in to see wholesale pricing” only if your wholesale flow actually works that way. If you use multiple tags (like wholesale and vip), decide which one wins, then use elsif in that order.
Conditional offers by location or currency
Location-based conditions are great for shipping notes, region-specific legal copy, or excluding an offer that doesn’t apply in certain countries.
In notification emails, you can often key off the shipping address:
{% if shipping_address and shipping_address.country_code == "US" %}
<p>Free returns in the U.S. for 30 days.</p>
{% endif %}
Currency-based conditions are useful when you display thresholds or promos that only make sense in one currency. Use them only when the currency field is reliably present in that template or automation. If you are not sure, gate it behind an existence check first.
Hiding blocks when data is unavailable
The fastest way to make an email look broken is to show labels without values. If a block depends on a variable, wrap the whole block.
Example for an optional delivery note:
{% if shipping_address and shipping_address.city != blank %}
<p>Delivering to {{ shipping_address.city }}.</p>
{% endif %}
If you’re looping line items or showing an order summary, also guard against empty arrays. It’s better to show nothing than to render a half-empty table.
Looping through products and line items without breaking layouts
Iterating line_items and collections
Loops are where Shopify Liquid gets powerful, and where email layouts can get messy fast. In notification emails, line_items is commonly available and is the safest place to start when you want to show what the customer bought.
A simple loop that stays layout-friendly:
{% if line_items and line_items.size > 0 %}
<ul>
{% for item in line_items %}
<li>{{ item.quantity }} × {{ item.title }}</li>
{% endfor %}
</ul>
{% endif %}
In Shopify Email, “collections” and “products” loops depend on what the template and automation provide. If you’re not sure a collection object exists, don’t build a layout that requires it. Use customer-based personalization first, then add product loops only when you can test them reliably in that email type.
Limiting output and keeping email HTML clean
Email space is limited, and long item lists can push your main CTA below the fold. Limit what you output:
{% assign max_items = 3 %}
{% for item in line_items limit: max_items %}
<p>{{ item.title }}</p>
{% endfor %}
If you need a “+ X more items” line, calculate it carefully and only show it when the count is higher than your limit:
{% assign remaining = line_items.size | minus: max_items %}
{% if remaining > 0 %}
<p>Plus {{ remaining }} more item{% if remaining != 1 %}s{% endif %}.</p>
{% endif %}
Keep HTML as simple as possible. Email clients are fragile. A basic table or list with minimal nesting will render more consistently than complex div grids.
Avoiding empty loops and trailing separators
Empty loops usually happen when you assume an order exists in a marketing email, or when the object name differs between templates. Always guard your loop with a size check, and don’t print separators blindly.
Bad pattern (creates trailing commas):
{% for tag in customer.tags %}{{ tag }}, {% endfor %}
Safer pattern using forloop.last:
{% if customer and customer.tags and customer.tags.size > 0 %}
<p>
{% for tag in customer.tags %}
{{ tag }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
</p>
{% endif %}
The same idea applies to line items. If you’re building a single-line summary like “Item A, Item B, Item C,” use forloop.last or a list format. It prevents the small formatting glitches that make personalization feel sloppy.
Date and time personalization in Shopify Liquid emails
Using now and date formatting
Dates are great for creating urgency and clarity in Shopify emails, as long as you format them consistently. In Liquid, you can output the current timestamp by piping the special keyword "now" (or "today") into the date filter.
For example, to show a friendly “today’s date” line:
Updated {{ "now" | date: "%B %d, %Y" }}
Common formats you’ll actually use in emails:
"%B %d, %Y"→ January 30, 2026"%b %d"→ Jan 30"%Y-%m-%d"→ 2026-01-30 (useful for internal-looking content, not marketing copy)
If you want to format an existing timestamp (like an order created date in a notification), you can pipe that value through the same filter:
Ordered on {{ created_at | date: "%B %d, %Y" }}
Shopify’s Liquid date filter follows strftime-style formatting, so you can build the exact output you need. If you ever want to double-check the format tokens, Shopify’s official reference for the Liquid date filter is the most reliable place to confirm syntax.
Time zone and scheduling considerations
Time personalization gets tricky when you assume the customer’s local time. Most Shopify email contexts don’t reliably provide a customer time zone, so “Today” and “Ends at 11:59 PM” can be ambiguous for international lists.
A safer approach:
- Prefer dates over exact times for broad sends (“Ends Feb 2”).
- If you must include a time, name the reference time zone (“11:59 PM ET”) and keep it consistent.
- For scheduled campaigns, remember that
"now"will reflect when the email is rendered and sent, not when the customer opens it. That’s usually what you want for “Sent on” style copy, but it’s not ideal for countdown-style messaging.
Testing, previewing, and privacy-safe personalization practices
Testing with multiple customer and order scenarios
Personalization breaks when you only test one “perfect” customer record. Build a small test checklist and run it before every send:
- A customer with a first name and last name
- A customer with no name fields at all
- A customer with tags (VIP/wholesale) and one without
- For order-based emails: an order with a shipping address, and one where key fields are blank (company, address line 2, province)
In Shopify Email, always send yourself a real inbox preview, not just an on-screen preview. Shopify Messaging supports sending test emails to multiple addresses, which is the fastest way to spot spacing issues, awkward fallbacks, and mobile layout problems in real clients like Gmail and Apple Mail. You can use Shopify’s own steps for sending a test email.
Avoiding sensitive data and compliance pitfalls
Treat Liquid personalization as “need to know.” Just because a field exists does not mean it belongs in an email.
Avoid putting sensitive or high-risk data in visible copy, including full street addresses in marketing emails, phone numbers, internal notes, or anything that could reveal personal traits. If you use tags for segmentation, keep tag names neutral. “VIP” is fine. Anything that implies health, finances, or other sensitive categories is a bad idea.
Also be careful with dynamic content that could expose order details to the wrong person in a forwarded email. Keep transactional specifics inside notification emails, and keep marketing emails more general.
Troubleshooting common rendering errors
Most Shopify Liquid email errors come from three issues:
- Using a variable that doesn’t exist in that email type
- Dot-chaining into nested objects without checking the parent first
- Loops that assume an array has items
When a block renders blank, simplify it. Print the smallest safe field first (like customer.email or shop.name), then add conditions and nested fields one at a time. This “build up” approach is also how you keep complex templates maintainable in Mailscribe-style workflows, where small edits happen often.
Related posts
Keep reading
How To Segment By Last Purchase Date For Smarter Winback Timing
Segment by last purchase date to build 30/60/90-day winback audiences, exclude new buyers, handle no-order contacts, and send offers when churn risk rises.
Email Marketing for SaaS Companies: A Comprehensive Guide
Email marketing for SaaS companies: build onboarding, trial-to-paid and retention flows with segmentation, automation, and deliverability basics to cut churn.
Tips for Creating Interactive Emails
Boost clicks and conversions with fun, mobile-friendly interactive emails using quizzes, GIFs, countdowns, polls, sliders, and AMP-powered dynamic content.
Incorporating Dark Mode in Email Design and Marketing
Dark mode email design tips to protect brand colors, keep text readable, handle logo/image inversion, add CSS where supported, and test in major clients.
How To Create A VIP Segment Using LTV And Refund Rate Thresholds
VIP segment criteria: set LTV cutoffs and refund-rate caps to tag high-value, low-risk customers, sync to email/SMS, and protect margins with tier rules.
How To Build A Sunset Policy For Inactive Subscribers That Works
Sunset policy for inactive subscribers: set clear inactivity criteria, run a brief re-engagement flow, then suppress or remove to protect deliverability.