{"title":"Cache - Liquid Tag Reference","slug":"cache-tag-reference","url":"https://support.storeconnect.com/articles/cache-tag-reference","url_markdown":"https://support.storeconnect.com/articles/cache-tag-reference.md","subtitle":null,"summary":"The `cache` block tag stores rendered output in the server-side cache so it can be reused without re-processing. Every `cache` block needs a cache key, plus the list of objects its output depends on.","type":"Developer_Documentation","video_url":"","keywords":"liquid, tags, cache, block tag, caching, performance, expires_in, storeconnect","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"The `cache` block tag stores rendered output in the server-side cache so it can be reused without re-processing. Wrap expensive queries, loops, or external calls in `{% cache %}...{% endcache %}` to cut page render time.\n\nEvery `cache` block needs a cache key as its first argument. A block with no key raises an error when the template renders.\n\n## Syntax\n\n\n```liquid\n\n{% cache \"\u003ckey\u003e\", items: [\u003cobjects\u003e], expires_in: \u003cseconds\u003e %}\n  \u003c!-- content to cache --\u003e\n{% endcache %}\n```\n\n\n| Property | Value |\n|----------|-------|\n| **Tag Name** | `cache` |\n| **Type** | Block tag |\n| **Source** | Hydrofoil (core) |\n\n## Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| Cache key | String | Required, passed as the first argument with no parameter name. A short name for the block, or a variable holding one. Rendering fails if it is missing or empty. |\n| `items` | Array | The objects the block's output depends on. Each one is added to the cache key so every product, customer, or cart gets its own cached copy. |\n| `expires_in` | Number | How long the cached output lives, in seconds. Defaults to 60 seconds. |\n| `race_condition_ttl` | Number | How long, in seconds, the expired copy keeps being served while a new one is generated. Defaults to 5 seconds. When `expires_in` is 5 seconds or less, this becomes half of `expires_in`. |\n| `force` | Boolean | Set `true` to skip the existing entry and rewrite it. Useful while developing. |\n| `skip_nil` | Boolean | Set `true` to leave the cache untouched when the block produces nothing. |\n\n## How the cache key is built\n\nThe final key combines the name you pass, the current store, and each entry in `items`. Two things follow from this:\n\n- **Keys are isolated automatically.** The same cache name in two different stores, or in two different Salesforce orgs, never collides. You do not need to add the store to the key yourself.\n- **`items` scopes the key, it mostly does not watch for edits.** Most objects contribute only their record ID, so passing `product` gives that product its own cached copy, but editing the product does not clear it. `current_cart` is the exception: it contributes a version that moves whenever the cart changes, so a block keyed on the cart refreshes as soon as the cart does.\n- **Expiry is the only general way out.** A `cache` block refreshes when `expires_in` elapses. There is no way to clear a single block on demand, so choose `expires_in` to match how quickly the content has to be correct.\n\nChanging the **Cache Version** field on the [**Store** record](store-object-reference) in Salesforce, which publishing a content change does for you, clears the store's page cache. It does not reach inside `cache` blocks: those are keyed separately and keep serving their copy until it expires.\n\n:::note\nCaching is bypassed while you preview an unpublished content change in Studio, so the editor always shows current content. A change that looks correct in the editor can still serve a cached copy on the live store until the block expires. See [changes not showing on the website](changes-not-showing-on-website).\n:::\n\n## Examples\n\n### Cache a product card\n\nPass the product so each one is cached separately, and the store and customer so that price, availability, and customer-specific text stay correct:\n\n\n```liquid\n\n{%- cache \"product\", items: [product, current_store, current_customer] -%}\n  \u003ch3\u003e{{ product.name }}\u003c/h3\u003e\n  {{ product.pricing.price | money }}\n  {%- render \"products/product/add_to_cart\", product: product -%}\n{%- endcache -%}\n```\n\n\n### Cache the header\n\nThe header shows the cart item count, so include `current_cart`. Without it, a customer's own header would keep the count it had when the block was cached, and signed-out visitors, who share a single key, would see each other's count:\n\n\n```liquid\n\n{%- cache \"header\", items: [current_store, current_customer, current_cart] -%}\n  {%- render \"menu\" -%}\n  \u003cspan class=\"SC-Cart_count\"\u003e{{ current_cart.item_count }}\u003c/span\u003e\n{%- endcache -%}\n```\n\n\n### Build the key from a variable, and hold it longer\n\nWhere a name alone is not specific enough, build the key first and pass the variable. Content that changes rarely can be held well beyond the default minute, so this related-products list is cached for ten minutes:\n\n\n```liquid\n\n{%- capture related_key %}related/{{ product.id }}{% endcapture -%}\n{%- cache related_key, items: [current_customer], expires_in: 600 -%}\n  {%- for related in product.related_products -%}\n    {%- render \"products/card\", product: related -%}\n  {%- endfor -%}\n{%- endcache -%}\n```\n\n\n### Cache an external call\n\nWrap an [`api` block](api-tag-reference) so the storefront does not call the service on every render:\n\n\n```liquid\n\n{%- cache \"forecast\", items: [current_store], expires_in: 900 -%}\n  {% api url: 'https://api.example.com/v1/forecast', method: 'GET', async: false %}\n    {% if response.status == 200 %}{{ response.body.summary }}{% endif %}\n  {% endapi %}\n{%- endcache -%}\n```\n\n\n## Additional recommendations\n\n- **Always pass the objects the output depends on.** The most common caching bug is omitting `current_customer` or `current_cart` from a block whose output differs per customer, which makes one customer's rendered output visible to everyone until it expires.\n- **Never cache a block containing a [`form` tag](form-tag-reference).** Forms include a hidden security token tied to the visitor's session, so a cached copy hands the next visitor a token that fails validation on submit. The same applies to anything else built from session values.\n- **Cache the expensive part, not the whole page.** Small blocks around slow queries and external calls give most of the benefit and stay correct for longer.\n- **Measure before and after.** Wrap the block in a [`timer` block](timer-tag-reference) to confirm it is worth caching.\n- **Keep expiry short while developing,** or pass `force: true`, so you are not debugging output that a cached copy is producing.\n\n:::warning\nPrices, stock levels, and cart contents stay stale for as long as the cached copy lives. Keep `expires_in` short on anything a customer makes a purchase decision from, and always include `current_customer` in `items` so that customer-specific pricing, approvals, and membership discounts are not shared between visitors.\n:::"}