It’s the Monday of a sale week. The campaign brief is in. By Friday, your team has built ten versions of the same promotion — one for Gold, one for Silver, one for Bronze; one for the metro markets, one for tier-2 cities; one for repeat buyers, one for cart abandoners. Same offer. Same hero image. Same CTA structure. The only thing changing between variants is a line of copy, a discount code, and the segment behind it.
Each variant gets its own brief, its own creative pull, its own QA pass, its own approval, its own send schedule. By the time the last variant clears review, the first one is already going out with segment data that’s two days old.
This isn’t a personalization problem. It’s a campaign authoring problem. And it’s exactly what Liquid Tags are built to fix.
What Liquid Tags Do — In One Sentence
Liquid Tags let you write one campaign that renders the right version for each recipient at the moment Netcore dispatches the message, based on that user’s current profile data.
A few details worth being precise about, because they matter for how you plan campaigns:
- Built on Shopify’s Liquid (Go library). Netcore CE uses the Shopify Liquid Go library under the hood — the same templating engine maintained by the Shopify ecosystem. So the syntax you learn is portable, the documentation you find online is relevant, and the language behaves predictably across our platform.
- Liquid does not replace your campaigns. You still build campaigns. You still pick audiences. Liquid changes what happens inside the campaign — collapsing the content variants you’d otherwise duplicate as separate sends.
With that grounded, here’s where it gets useful.
The Three Building Blocks
Liquid Tags have many capabilities, but 90% of the marketer-facing value lives in three primitives: conditionals, filters, and loops. Below, each one is explained through the campaigns you actually run.

1. Conditionals — One Template, Many Versions
The if / elsif / else block lets you gate any piece of content behind a data condition. Every “we built four variants of this campaign” situation collapses into a single template with conditional branches.
Use cases that show up in almost every CRM team:
Loyalty-tier offers in one campaign
You’re running a Diwali sale. Instead of three campaigns:
{% if user.tier == "gold" %}
Early access starts now. {{ user.points }} pts → 25% off everything.
{% elsif user.tier == "silver" %}
You're {{ user.points_to_gold }} pts from Gold. Earn 2x today + 20% off.
{% else %}
Diwali sale is live — up to 20% off across the store.
{% endif %}
One brief, one creative review, one send schedule. Three tier-appropriate messages.
Region-specific promo codes inside a national campaign
Hi {{ user.first_name | capitalize }}, your city pack is here.
{% if user.city == "Mumbai" %}
Use code MUM20 — free delivery across South Mumbai today.
{% elsif user.city == "Bengaluru" %}
Use code BLR20 — same-day delivery in Indiranagar & Koramangala.
{% else %}
Use code FEST20 — applies nationwide.
{% endif %}
Same national push. No “build a Mumbai version” ticket.
Lifecycle-aware CTAs
A subscription product. The same monthly newsletter has to say different things depending on where each user is in their lifecycle:
{% if user.subscription_expiry_days < 7 %}
Your plan renews in {{ user.subscription_expiry_days }} days.
Renew now & lock in current pricing.
{% elsif user.last_login_days > 30 %}
We miss you. Here's what's new since you were last in.
{% else %}
This month's edit: {{ featured_collection_name }}.
{% endif %}
No more “we need a separate renewal reminder campaign and a separate win-back campaign and a separate active-user newsletter.”
Suppression without rebuilding the audience
Hide a referral CTA from users who’ve already referred 3+ friends:
{% if user.referrals_made < 3 %}
Refer a friend, earn ₹200. You've referred {{ user.referrals_made }} so far.
{% endif %}
The audience stays broad. The CTA disappears for the people who don’t need to see it.
Stacked conditions for genuinely complex logic
{% if user.tier=="gold" and user.last_order_days < 30 and user.city=="Mumbai" %}
Your Gold pickup pass is live at our Bandra store.
{% elsif user.tier == "gold" and user.last_order_days < 30 %}
Your Gold member-only line is open this weekend at all stores.
{% endif %}
This kind of logic used to mean either (a) three separate campaigns with three separate segments, or (b) an engineering ticket. Now it’s three lines in a template.
The campaigns this collapses: tier campaigns, region campaigns, lifecycle stage campaigns, suppression segments, A/B copy variants where the only variable is one user attribute. If you’re currently building parallel versions of the same campaign across any of these dimensions, conditionals replace that work.

2. Filters — Make Raw Data Send-Ready
Data in your user profiles rarely arrives in message-ready format. Names are lowercased. Prices are raw floats. Dates are Unix timestamps. Product titles run 80 characters.
Without filters, the marketer’s only options are: ship sloppy formatting, or open a ticket asking engineering to clean the data upstream. Filters fix it inline, in the template, at the moment of render.
The filters marketers reach for daily
| Filter | What it does | Example |
|---|---|---|
| capitalize | Fixes case on names | “sarah” → “Sarah” |
| default | Fallback for missing values | empty → “there” |
| round / money_format | Cleans up numeric values | 1299.9 → ₹1,299.90 |
| date | Localizes timestamps | 1718150400 → “June 12, 2025” |
| truncate | Caps long strings | 80-char title → 30 chars + … |
| upcase / downcase | Standardizes casing | “DIWALI” → “diwali” |
| plus / minus / times | Inline math | points – threshold → “you’re 200 pts away” |
What this looks like in a real message
Before filters:
"hi sarah, your gift card balance is 35.5 and it expires on 1718150400"
After filters:
Hi {{ user.first_name | capitalize | default: "there" }},
your gift card balance is {{ user.gift_card_balance | money_format }}
and expires on {{ user.expiry_timestamp | date: "%B %d, %Y" }}.
"Hi Sarah, your gift card balance is ₹35.50 and expires on June 12, 2025."
A note on filter coverage
Because Netcore uses the Shopify Liquid Go library, the standard set of Shopify Liquid filters — strings, math, arrays, dates — is supported out of the box, plus Netcore-specific filters layered on top (like money_format for Indian currency formatting). The full supported list with examples and edge cases is in our Liquid Tags documentation. If a filter you need isn’t there yet, your CSM has a direct line to flag it — the set expands based on real customer requests.
The work this saves: every “can engineering clean this field before send” ticket. Every “we’ll just live with the messy formatting this time” compromise. Every QA cycle where someone catches “₹1299.9” in a preview and the send gets pushed by a day.
3. Loops — Dynamic Lists Without Hard-Coded Lengths
Loops iterate through an array and render the same layout block for each item. This is how you build any campaign where the content is “a list of things, and the list changes per user.”
Abandoned cart emails that adapt to cart size
You left {{ cart.items.size }} items in your cart:
{% for item in cart.items %}
• {{ item.name | truncate: 40 }} — {{ item.price | money_format }}
{% endfor %}
Total: {{ cart.total | money_format }}
A user with 2 items gets 2 lines. A user with 7 items gets 7 lines. Same template. No “what if they have more than 5 products” edge case.
Personalized product recommendations
Picked for you based on what you've been browsing:
{% for product in user.recommendations %}
{{ forloop.index }}. {{ product.name | capitalize | truncate: 30 }}
{{ product.price | money_format }} — {{ product.url }}
{% endfor %}
Pull the top N items from your recommendation engine. The template renders 1, 3, 5, however many come back. Add or reorder the source array — the message follows.
Order summaries, wishlist nudges, watchlist updates
Anything list-shaped. Order confirmation emails that itemize the full order. Wishlist back-in-stock nudges that show every item that returned. Watchlist updates for a streaming app listing every new episode on the user’s tracked shows. Replenishment reminders for an e-commerce brand listing every consumable a user is due to reorder.
Before loops, each of these required either a hard-coded “top 3” layout (and a static creative redesign every time the count changed), or a specialist building a custom template per use case. Now it’s one block that adapts.
Loops with conditionals nested inside
{% for item in cart.items %}
• {{ item.name }} —
{% if item.on_sale %}
<s>{{ item.original_price | money_format }}</s>
{{ item.sale_price | money_format }} (sale)
{% else %}
{{ item.price | money_format }}
{% endif %}
{% endfor %}
The combination is where real campaign-quality lifts come from — every list item gets its own conditional treatment, all rendered at send time, all from one template.

What All of This Saves You — Concretely
Liquid Tags don’t replace campaign building. They change the shape of campaign building. Here’s where the time and quality wins land.
Time-to-launch shrinks
A campaign that previously needed 10 variants — each with a separate brief, creative pull, QA cycle, and approval — now needs one template with branching logic. The reduction in build cycles is the largest single time win most teams see.
Typical pattern from teams that have made the shift:
- Brief time: 1 brief instead of N. Cuts coordination overhead with creative and stakeholders.
- Creative production: 1 set of assets instead of N near-identical sets.
- QA: 1 template to review with each branch previewed — instead of N separate variant reviews.
- Approval: 1 approval cycle instead of N.
- Send setup: 1 send instead of N parallel sends with N segment definitions.
For high-frequency campaigns (weekly newsletters, lifecycle flows, sale-week pushes), the savings compound every cycle.
Campaign quality goes up
Because every variant lives in one template, you stop seeing the “drift” problem — where the Gold version of a campaign has a typo the Silver version doesn’t, or the Mumbai variant has an updated CTA that the Bengaluru variant missed. One source of truth means one place to fix issues, one place to update copy, one preview surface to test.
Marketers also stop making the small compromises that come from “we don’t have time to build a separate variant for this, so we’ll just send the generic version.” With conditionals, the variant is the campaign — no shortcut needed.
Send-time freshness
This is the part most segmentation-only workflows lose. When you build separate campaigns for separate segments, the segment is frozen at the moment the campaign goes into the send queue — which can be hours or days before the actual dispatch. A user who upgraded from Silver to Gold yesterday morning still gets the Silver message tonight.
Because Liquid evaluates at send time, against the user’s current profile, that drift disappears. A Gold user gets the Gold message. A user who just lapsed gets the lapsed-user CTA. The logic resolves at the moment of dispatch, not at build time. (And for attributes that need to resolve at the very moment of send — live pricing, current inventory, today’s recommendation — Content Fetch calls an external API at dispatch and feeds the result into your Liquid template. The two features pair together.)
Experimentation gets cheaper
Want to test a new conditional branch? Add an {% elsif %} to the existing template. No campaign duplication. No parallel send. No segment definition. The experiment runs inside the campaign you’re already shipping.
Engineering stops being the bottleneck
The historical pattern: marketer wants a new conditional → opens a ticket → waits two sprints → conditional ships → marketer wants to tweak it → opens another ticket. With Liquid + Co-Marketer (covered below), most conditional changes happen entirely inside the marketer’s workflow. Engineering gets to focus on the data layer underneath, not on copy logic.
The Authoring Layer: Co-Marketer
The honest part: Liquid is powerful, and it’s still code. Writing {% if user.tier == “gold” and user.last_order_days < 30 %} is not what most CRM managers signed up for when they took the role.
The historical workarounds all have ceilings. Basic merge tags are easy but capped at first-name personalization. Leaning on engineering gives you full power but every change is a ticket. No-code conditional builders are accessible but the abstraction caps what’s possible — the moment you want nested logic or a custom filter, you’re back to opening tickets.
Co-Marketer is the path through this. Describe what you want in plain English. Co-Marketer generates the Liquid for you.
Input: "Show a renewal reminder if the subscription expires in less than 7 days,
otherwise show the standard offer with the user's loyalty tier mentioned." Co-Marketer output: A validated, syntactically correct Liquid block referencing
the right user attributes, with a fallback path defined. Drop it into
your template and ship.
Two things Co-Marketer enforces automatically that protect campaigns at scale:
- Forced fallbacks. Before generating any conditional, Co-Marketer makes you define what happens when the data is missing. A message that renders an empty loyalty balance looks worse than no personalization at all — this prevents it.
- PII safety by default. Co-Marketer references attributes by schema, not by value. It never reads the actual contents of user records during authoring. Compliance guardrails stay intact.
And — importantly — the output is real, inspectable Liquid. Not a black-box abstraction. The logic is portable, your team builds transferable skill (the same Liquid syntax works on Shopify, Braze, and anywhere else the language is supported), and when something doesn’t render as expected, you can read the code and see exactly why.
How Netcore’s Implementation Compares
| Netcore | Braze | MoEngage | CleverTap | |
|---|---|---|---|---|
| Templating language | Liquid (Shopify Go library) | Liquid | Jinja | Liquid |
| AI authoring | Co-Marketer (NL → Liquid) | Limited | Limited | Manual |
| Send-time API fetch | Content Fetch | Connected Content | Available | Advanced plan only |
| Fallback handling | Forced fallbacks + NFR | Author-defined filter | Author-defined filter | Not specified |
| Channel coverage | Email, Push, SMS, WhatsApp, RCS, In-App, Web | Most channels | Most channels | Most channels |
Other platforms with Liquid still require marketers to write and validate the syntax manually. No-code builders abstract the syntax but cap what’s possible. Netcore combines the full expressiveness of Liquid — backed by the Shopify Go library — with an AI authoring layer that any marketer can use.
One Caveat Worth Naming: Data Quality
Liquid Tags are a logic layer. They faithfully render whatever’s in the user profile — including stale or missing data. Before you build complex conditional logic, run a quick check:
- Are the attributes you want to personalize against actually current in the platform?
- Are loyalty tier updates flowing in real time, or on a nightly batch?
- Are behavioral attributes refreshed fast enough to be meaningful at send time?
If the answer is “not current enough” — the higher-impact fix isn’t more conditionals. It’s making sure the data resolves at the moment of send. That’s what Content Fetch does: it calls an external API at send time, pulls the latest values, and feeds them into your Liquid template.
For attributes that are already accurate and live in your user profiles, Liquid Tags are ready to go today.
Where to Start
The fastest path to value, in order:
- Pick your most-cloned campaign — the one that exists in 5+ near-identical variants today. Tier-based promotions and region-based offers are usually the easiest wins.
- Collapse the variants with a conditional. Use Co-Marketer to draft the Liquid; don’t write it from scratch.
- Clean up rendering with filters — names, currency, dates. Small wins that compound across every campaign you ship.
- Replace a static 3-product block with a loop — recommendation rails, cart contents, wishlist reminders. This is where the dynamic feel kicks in.
- Stack conditionals inside loops for the campaigns that genuinely need it — sale-aware cart emails, lifecycle-aware order confirmations, region-aware recommendation rails.
Each step is independently valuable. Together, they take you from “we build N variants per promotion” to “we build one template that renders N ways at send time.”

The Shift
Segmentation answers who gets the campaign. Liquid Tags answer what each person in that campaign actually sees, at the moment we dispatch their message. Both still matter. Your audiences still need to be defined. Your campaigns still need to be authored, scheduled, and approved.
What changes is the cloning. The duplicate creative pulls. The parallel QA cycles. The frozen segment data sitting in a queue while a user’s profile moves on. The engineering tickets for every new conditional. The compromises you make when you don’t have time to build the “right” variant for a slice of your audience.
Fewer campaigns to clone. Faster launches. Cleaner messages. Logic that resolves against fresh data at dispatch. One template doing the work of many — authored in plain English, rendered at send time, running on the same Liquid engine that powers Shopify.
That’s the unlock.





