Mailscribe

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, and customer.name for greetings and subject lines.
  • customer.email when you need to confirm which inbox you’re writing to (use sparingly in visible copy).
  • customer.tags for 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.

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