{"title":"Liquid tags reference","slug":"liquid-tags-reference","url":"https://support.storeconnect.com/articles/liquid-tags-reference","url_markdown":"https://support.storeconnect.com/articles/liquid-tags-reference.md","subtitle":null,"summary":"Complete reference for all Liquid tags available in StoreConnect templates, including standard control flow and iteration tags, variable tags, template structure tags, data and integration tags, state management, caching, pagination, controller lifecycle, and debugging.","type":"Developer_Documentation","video_url":"","keywords":"liquid tags, if tag, for tag, unless tag, assign tag, capture tag, render tag, layout tag, require tag, component tag, form tag, query tag, paginate tag, cache tag, api tag, before tag, after tag, final tag, params tag, variables tag, redirect tag, respond tag, action tag, debug tag, timer tag, session tag, struct tag, new tag, liquid reference","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"Liquid tags use the `{% %}` syntax and perform actions rather than output values. StoreConnect extends standard Liquid with its own tags for template structure, data querying, HTTP responses, caching, and more.\n\nTags come in two forms:\n\n- **Simple tags** — self-contained: `{% tag_name options %}`\n- **Block tags** — open/close pair wrapping content: `{% tag_name %}...{% endtag_name %}`\n\nFor the index of StoreConnect-specific tags grouped by category, see [Liquid tags](liquid-tags).\n\n---\n\n## Control flow\n\n### `if` / `elsif` / `else`\n\nExecutes a block when a condition is true.\n\n\n```liquid\n\n{% if product.available %}\n  \u003cp\u003eIn stock\u003c/p\u003e\n{% elsif product.pricing.on_sale? %}\n  \u003cp\u003eOn sale\u003c/p\u003e\n{% else %}\n  \u003cp\u003eOut of stock\u003c/p\u003e\n{% endif %}\n```\n\n\n**Comparison operators:** `==`, `!=`, `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, `contains`\n\n**Logical operators:** `and`, `or`\n\n\n```liquid\n\n{% if product.available and product.pricing.price \u003c 100 %}\n  \u003cp\u003eAffordable and in stock\u003c/p\u003e\n{% endif %}\n\n{% if product.tags contains \"sale\" %}\n  \u003cspan class=\"badge\"\u003eSale\u003c/span\u003e\n{% endif %}\n```\n\n\n### `unless`\n\nExecutes a block when a condition is **false**. Does not support `elsif`.\n\n\n```liquid\n\n{% unless current_customer %}\n  \u003ca href=\"{{ current_store.login_path }}\"\u003eLog in\u003c/a\u003e\n{% endunless %}\n```\n\n\n### `case` / `when`\n\nSwitch statement for matching values.\n\n\n```liquid\n\n{% case current_checkout_step %}\n{% when \"customer_information\" %}\n  {% render \"checkout/customer_information\" %}\n{% when \"shipping_information\" %}\n  {% render \"checkout/shipping_information\" %}\n{% else %}\n  {% render \"checkout/default\" %}\n{% endcase %}\n```\n\n\nMultiple values: `{% when \"monday\", \"tuesday\", \"wednesday\" %}`\n\n---\n\n## Iteration\n\n### `for`\n\nIterates over a collection.\n\n\n```liquid\n\n{% for product in all_products %}\n  \u003cp\u003e{{ product.name }}\u003c/p\u003e\n{% endfor %}\n```\n\n\n**Parameters:**\n\n| Parameter | Description |\n|-----------|-------------|\n| `limit` | Maximum iterations: `{% for item in items limit: 5 %}` |\n| `offset` | Skip items: `{% for item in items offset: 3 %}` |\n| `reversed` | Reverse order: `{% for item in items reversed %}` |\n\n**Range:** `{% for i in (1..10) %}{{ i }}{% endfor %}`\n\n**`forloop` object:**\n\n| Property | Description |\n|----------|-------------|\n| `forloop.index` | Current iteration (1-based) |\n| `forloop.index0` | Current iteration (0-based) |\n| `forloop.rindex` | Remaining iterations (1-based) |\n| `forloop.rindex0` | Remaining iterations (0-based) |\n| `forloop.first` | True on first iteration |\n| `forloop.last` | True on last iteration |\n| `forloop.length` | Total number of iterations |\n\n**Empty fallback:**\n\n\n```liquid\n\n{% for product in collection %}\n  {{ product.name }}\n{% else %}\n  \u003cp\u003eNo products found\u003c/p\u003e\n{% endfor %}\n```\n\n\n### `break` / `continue`\n\n\n```liquid\n\n{% for item in items %}\n  {% if item.hidden? %}{% continue %}{% endif %}\n  {% if forloop.index \u003e 10 %}{% break %}{% endif %}\n  {{ item.name }}\n{% endfor %}\n```\n\n\n### `tablerow`\n\nGenerates HTML table rows.\n\n\n```liquid\n\n\u003ctable\u003e\n  {% tablerow product in all_products cols: 3 %}\n    {{ product.name }}\n  {% endtablerow %}\n\u003c/table\u003e\n```\n\n\nParameters: `cols`, `limit`, `offset`, `range`\n\n---\n\n## Variable\n\n### `assign`\n\nAssigns a value to a variable.\n\n\n```liquid\n\n{% assign my_variable = \"Hello\" %}\n{% assign product_count = all_products.size %}\n{% assign is_sale = product.pricing.on_sale? %}\n```\n\n\n### `capture`\n\nCaptures rendered content into a variable.\n\n\n```liquid\n\n{% capture full_name %}{{ current_customer.firstname }} {{ current_customer.lastname }}{% endcapture %}\n\u003cp\u003eWelcome, {{ full_name }}\u003c/p\u003e\n```\n\n\n### `increment` / `decrement`\n\nCreates and increments/decrements a named counter. The counter is independent of variables with the same name.\n\n\n```liquid\n\n{% increment counter %}\n{% increment counter %}\n{% increment counter %}\n```\n\n\n### `default`\n\nSets default values for variables that are not already defined. Only runs if the variable doesn't exist — does not override passed values. Commonly used at the top of snippets to define parameter defaults.\n\n\n```liquid\n\n{% default title: \"Untitled\", show_price: true, max_items: 10 %}\n```\n\n\n### `new`\n\nCreates new objects.\n\n**UUID:**\n\n\n```liquid\n\n{% new UUID my_id %}\n{{ my_id }}\n```\n\n\n**List:**\n\n\n```liquid\n\n{% new List my_list %}\n{% new List my_list = \"[1,2,3]\" %}\n```\n\n\n**Map:**\n\n\n```liquid\n\n{% new Map my_map %}\n{% new Map config = '{\"theme\":\"dark\"}' %}\n{{ config.theme }}\n```\n\n\n**Random number:**\n\n\n```liquid\n\n{% new Rand dice, min: 1, max: 6 %}\n{{ dice }}\n```\n\n\n### `struct`\n\nCreates a validated structured object.\n\n\n```liquid\n\n{% struct my_obj = \"struct_name\", key: \"value\", count: 42 %}\n```\n\n\n---\n\n## Template\n\n### `comment`\n\nPrevents content from rendering.\n\n\n```liquid\n\n{% comment %}\n  This won't be output\n{% endcomment %}\n\n{%- comment -%} Inline comment {%- endcomment -%}\n```\n\n\nInline form: `{%# This is a comment %}`\n\n### `raw`\n\nTemporarily disables Liquid processing so `{{ }}` and `{% %}` syntax passes through as literal text.\n\n```liquid\n\n\n  {{ this will not be processed }}\n\n```\n\n### `render`\n\nRenders a snippet or partial template.\n\n\n```liquid\n\n{% render \"header\" %}\n{% render \"products/card\", product: product, show_price: true %}\n```\n\n\nVariables are passed as named parameters. The snippet runs in its own scope — only passed variables are available.\n\n### `layout`\n\nSpecifies which layout wraps the current page template. If omitted, the default `theme.liquid` layout is used.\n\n\n```liquid\n\n{% layout \"account\" %}\n```\n\n\n### `require`\n\nLoads a CSS or JavaScript asset. Automatically deduplicates — the same asset is only loaded once per page regardless of how many templates request it.\n\n\n```liquid\n\n{% require \"styles/theme.css\" %}\n{% require \"scripts/theme.js\" %}\n```\n\n\nAlias: `{% resource_path \"styles/theme.css\" %}`\n\n### `header`\n\nSets HTTP response headers.\n\n\n```liquid\n\n{% header name: \"Content-Type\", value: \"application/json\" %}\n{% header name: \"Cache-Control\", value: \"public, max-age=3600\" %}\n```\n\n\n### `component`\n\nRenders a reusable component with support for lazy loading and event-driven reload.\n\n\n```liquid\n\n{% component \"cart\", reload: \"sc.cart-updated\" %}\n{% component \"checkout/vouchers\", reload: \"sc.voucher-applied sc.voucher-removed\", lazy: true %}\n```\n\n\n| Parameter | Description |\n|-----------|-------------|\n| First argument | Component template name (in `components/` directory) |\n| `reload` | Space-separated event names that trigger a reload |\n| `lazy` | If `true`, loads asynchronously after page load |\n| Additional params | Passed to the component template |\n\n### `form`\n\nWraps a block in an HTML form with CSRF protection and field definitions.\n\n\n```liquid\n\n{% form \"add-to-cart\", product: current_product %}\n  \u003cinput type=\"number\" name=\"{{ form.quantity.name }}\" value=\"1\"\u003e\n  \u003cbutton type=\"submit\"\u003eAdd to cart\u003c/button\u003e\n{% endform %}\n```\n\n\nInside the block, the `form` variable provides field definitions and errors. See [Theme forms](theme-forms) and [Liquid forms reference](liquid-forms-reference).\n\n### `paginate`\n\nPaginates a collection with configurable page size and navigation window.\n\n\n```liquid\n\n{% paginate all_products by 20, window: 5 %}\n  {% for product in all_products %}\n    {{ product.name }}\n  {% endfor %}\n\n  {% if paginate.pages \u003e 1 %}\n    {% for part in paginate.parts %}\n      {% if part.gap? %}...\n      {% elsif part.current? %}\u003cstrong\u003e{{ part.page }}\u003c/strong\u003e\n      {% else %}\u003ca href=\"{{ part.url }}\"\u003e{{ part.page }}\u003c/a\u003e\n      {% endif %}\n    {% endfor %}\n  {% endif %}\n{% endpaginate %}\n```\n\n\n| Parameter | Description |\n|-----------|-------------|\n| `by` | Items per page (required) |\n| `as` | Custom URL parameter name (default: `\"page\"`) |\n| `window` | Number of page links on each side (default: `5`) |\n\n**`paginate` drop properties:** `page_size`, `current_page`, `pages`, `records`, `current_offset`, `next`, `previous`, `first`, `last`, `parts`\n\n**Part properties:** `url`, `page`, `current?`, `gap?`\n\n### `cache`\n\nCaches a block of rendered HTML for a configurable duration.\n\n\n```liquid\n\n{% cache \"product-card\", items: [current_product, current_store], expires_in: 60 %}\n  {% render \"products/card\", product: current_product %}\n{% endcache %}\n```\n\n\n| Parameter | Description |\n|-----------|-------------|\n| First argument | Cache key name (required) |\n| `items` | Array of objects used to generate the cache key |\n| `expires_in` | Expiration in seconds |\n| `race_condition_ttl` | Race condition TTL in seconds |\n\n### `process_event`\n\nProcesses an event object and injects its methods into the template context.\n\n\n```liquid\n\n{% process_event order_event %}\n  \u003cp\u003e{{ event_type }}: order #{{ order_number }}\u003c/p\u003e\n{% endprocess_event %}\n```\n\n\n---\n\n## Data \u0026 integration\n\n### `query`\n\nFetches records from the StoreConnect data store and assigns them to a variable.\n\n\n```liquid\n\n{% query 'Product2' as featured, Featured__c: true order by 'Name asc' %}\n{% for record in featured %}\n  {{ record.Name }}\n{% endfor %}\n```\n\n\n**Syntax:** `{% query 'ObjectName' as variable [, field: value] [order by 'field asc|desc'] %}`\n\nRecords from `{% query %}` are raw record objects. Use `| cast: \"TypeName\"` to convert them to drops with full property access.\n\n\n```liquid\n\n{% query 'Product2' as records, IsActive: true order by 'CreatedDate desc' %}\n{% for record in records %}\n  {% assign product = record | cast: \"Product\" %}\n  {{ product.name }} — {{ product.pricing.price | money }}\n{% endfor %}\n```\n\n\nSee [Liquid query](liquid-query) for full usage guidance.\n\n### `api`\n\nMakes an outbound HTTP request to an external service.\n\n\n```liquid\n\n{%- new Map post_data -%}\n{%- assign post_data = post_data | set_key: \"event\", \"signup\" | set_key: \"email\", current_customer.email -%}\n{% api url: \"https://api.example.com/track\", method: \"post\", data: post_data %}\n  {% if response.status == 200 %}\n    \u003cp\u003e{{ response.body.message }}\u003c/p\u003e\n  {% endif %}\n{% endapi %}\n```\n\n\n| Parameter | Description |\n|-----------|-------------|\n| `url` / `endpoint` | Target URL (required) |\n| `method` | HTTP method: `get`, `post`, `put`, `patch`, `delete` (default: `get`) |\n| `data` | Request body — must be a Map variable, not inline JSON |\n| `headers` | Custom HTTP headers |\n| `username` / `password` | Basic authentication |\n| `bearer` | Bearer token |\n| `async` | Run in background (forced `true` inside `{% final %}`) |\n\n**Response object:** `response.status`, `response.body`, `response.headers`. JSON responses are automatically parsed. Async requests don't expose response data.\n\n---\n\n## State management\n\n### `session`\n\nStores values in the user's session, persisting them across requests.\n\n\n```liquid\n\n{% session last_viewed: product.id, preference: \"compact\" %}\n```\n\n\nAccess stored values with `session_variables`:\n\n\n```liquid\n\n{{ session_variables.last_viewed }}\n{{ session_variables.preference }}\n```\n\n\n### `context`\n\nSets variables scoped to a component. Only effective inside component templates. Context variables persist across reloads and don't leak to parent or sibling components.\n\n\n```liquid\n\n{% context product_id: current_product.id, show_details: true %}\n```\n\n\n---\n\n## Controller lifecycle\n\nThese block tags run only during a specific phase of a Liquid controller's execution. See [Liquid controllers guide](liquid-controllers-guide) for full usage.\n\n### `before`\n\nRuns before the page template renders.\n\n\n```liquid\n\n{% before %}\n  {% params product_id: current_request.params.id %}\n  {% action \"cart.add\", product_identifier: product_id, quantity: 1 %}\n{% endbefore %}\n```\n\n\n### `after`\n\nRuns after the main controller action.\n\n\n```liquid\n\n{% after %}\n  {% redirect to: \"/cart\", notice: \"Item added\" %}\n{% endafter %}\n```\n\n\n### `final`\n\nRuns after the response is sent. API calls here are automatically async.\n\n\n```liquid\n\n{% final %}\n  {%- new Map payload -%}\n  {%- assign payload = payload | set_key: \"event\", \"purchase\" -%}\n  {% api url: \"https://analytics.example.com/track\", method: \"post\", data: payload %}{% endapi %}\n{% endfinal %}\n```\n\n\n### Action tags (inside phases)\n\nThese tags are valid only inside `{% before %}`, `{% after %}`, or `{% final %}` blocks.\n\n**`{% params %}`** — reads request parameters into controller params.\n\n**`{% variables %}`** — sets template variables available in the page template.\n\n**`{% redirect %}`** — redirects and stops execution. Options: `to`, `notice`, `alert`, `status`.\n\n**`{% respond %}`** — sends a custom HTTP response. Options: `status`, `body`, `json`.\n\n**`{% update %}`** — updates a custom data field on an object.\n\n**`{% action %}`** — executes a named action. Cart: `cart.add`, `cart.update`, `cart.remove`, `cart.empty`, `cart.select`, `cart.clone`. Shipping: `shipping.set`. Pricebook: `pricebook.set`, `pricebook.clear`. Promotion: `promotion.apply`, `promotion.remove`, `promotion.clear`.\n\n---\n\n## Debugging\n\n### `debug`\n\nLogs variable values to the session debug output.\n\n\n```liquid\n\n{% debug product_id: product.id, cart_items: current_cart.item_count %}\n```\n\n\n### `timer`\n\nMeasures and logs the execution time of a template block.\n\n\n```liquid\n\n{% timer \"product_list_render\" %}\n  {% for product in all_products %}\n    {% render \"products/card\", product: product %}\n  {% endfor %}\n{% endtimer %}\n```\n\n\n---\n\n## Whitespace control\n\nAdd `-` to tag delimiters to strip surrounding whitespace:\n\n\n```liquid\n\n{%- assign x = \"hello\" -%}\n{%- if true -%}Content{%- endif -%}\n```\n"}