presentational-vs-logic
TL;DR
Liquid is NOT purely presentational, but its logic is intentionally constrained. It supports:
- Conditionals (
if/else/unless) - Loops (
for) - Variable assignment (
assign,capture) - Filters (transformations)
It prohibits:
- Custom functions
- State management
- Direct API calls
- Complex algorithms/recursion
- Backend logic access
Design Philosophy
Liquid was designed with a specific security model in mind:
“Liquid needs to be non-evaling and secure. Liquid templates are made so that users can edit them. You don’t want to run code on your server which your users wrote.” — Shopify/liquid GitHub
Core Principles
- Non-evaluating: No arbitrary code execution
- Stateless: No concept of persistent state between renders
- Sandboxed: Users cannot access backend systems
- Separated compile/render: Expensive parsing done once, then render with passed data
Logic Capabilities
What Liquid CAN Do
| Feature | Syntax | Purpose |
|---|---|---|
| Conditionals | {% if %}, {% unless %}, {% case %} | Control flow |
| Loops | {% for item in collection %} | Iteration |
| Variables | {% assign x = value %} | Local assignment |
| Capture | {% capture var %}...{% endcapture %} | String building |
| Filters | {{ value | upcase | truncate: 20 }} | Transformations |
Logic Example
{% if product.available %} {% for variant in product.variants %} {% if variant.inventory_quantity > 0 %} <option value="{{ variant.id }}"> {{ variant.title }} - {{ variant.price | money }} </option> {% endif %} {% endfor %}{% else %} <p>Out of stock</p>{% endif %}This demonstrates real logic — conditionals, loops, comparisons — not just variable interpolation.
Limitations (By Design)
No Custom Functions
Liquid only provides predefined tags and filters. You cannot define:
{% comment %} NOT POSSIBLE {% endcomment %}{% function calculate_discount(price, percent) %} ...{% endfunction %}No State
Each render is isolated. No persistence between requests:
{% comment %} NOT POSSIBLE {% endcomment %}{% increment_global_counter %}{% remember user_preference %}No API Calls
Liquid cannot fetch external data:
{% comment %} NOT POSSIBLE {% endcomment %}{% fetch 'https://api.example.com/data' %}Data fetching is a JavaScript task or must come from Shopify’s backend.
Limited Operators
- No parentheses for grouping:
{% if (a and b) or c %}is invalid - Operators evaluated right-to-left with
and/or - Cannot check for objects in arrays of objects with
contains
No Complex Algorithms
No recursion, no while loops, limited mathematical operations.
Where Business Logic Lives
┌─────────────────────────────────────────────────────────┐│ Shopify Backend ││ (Ruby/GraphQL - Real business logic) ││ • Inventory management ││ • Payment processing ││ • Discount calculations ││ • Order workflows │└─────────────────────────────────────────────────────────┘ │ ▼ (provides data)┌─────────────────────────────────────────────────────────┐│ Liquid Templates ││ (Presentation + Display Logic) ││ • Render product info ││ • Show/hide elements conditionally ││ • Format prices, dates ││ • Loop through collections │└─────────────────────────────────────────────────────────┘ │ ▼ (outputs HTML)┌─────────────────────────────────────────────────────────┐│ Browser/JavaScript ││ (Client-side interactivity) ││ • Event handling ││ • Animations ││ • AJAX/API calls ││ • Dynamic updates │└─────────────────────────────────────────────────────────┘Security Model
Liquid’s constraints are its security features:
| Constraint | Security Benefit |
|---|---|
| No eval | Prevents code injection |
| No file access | Prevents data exfiltration |
| No network access | Prevents SSRF attacks |
| Stateless | Prevents session hijacking |
| Limited operations | Prevents DoS via computation |
This allows Shopify to safely let merchants edit templates without risking server security.
Comparison with Other Template Languages
| Language | User-Safe | Logic | State | Functions |
|---|---|---|---|---|
| Liquid | Yes | Limited | No | No |
| Jinja2 | With sandbox | Full | Yes | Yes |
| ERB | No | Full | Yes | Yes |
| Mustache | Yes | Minimal | No | No |
| Handlebars | Partial | Limited | No | Helpers |
| Nunjucks | No | Full | Yes | Yes |
Liquid and Mustache are designed for untrusted users. Others require trusted template authors.
Practical Implications
What This Means for Development
- Theme development: Liquid handles presentation layer effectively
- Complex logic: Must be in Shopify Functions, Apps, or JavaScript
- Data transformation: Use filters, but heavy lifting done server-side
- Performance: Complex Liquid loops can slow rendering at scale
When Liquid Falls Short
For these use cases, you need Shopify Apps or Functions:
- Custom pricing logic
- Complex discount rules
- External API integrations
- Real-time inventory from third parties
- Custom checkout modifications
Conclusion
Liquid occupies a middle ground:
- More than a simple interpolation engine (has real logic)
- Less than a general-purpose programming language (intentionally limited)
It’s templating with guardrails — designed for safe, user-editable presentation with enough logic to handle display decisions, but not enough to become a security liability.