# Cache - Liquid Tag Reference

Source: https://support.storeconnect.com/articles/cache-tag-reference · Last modified 21 August 2026

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.

Every `cache` block needs a cache key as its first argument. A block with no key raises an error when the template renders.

## Syntax


```liquid

{% cache "<key>", items: [<objects>], expires_in: <seconds> %}
  <!-- content to cache -->
{% endcache %}
```


| Property | Value |
|----------|-------|
| **Tag Name** | `cache` |
| **Type** | Block tag |
| **Source** | Hydrofoil (core) |

## Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| 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. |
| `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. |
| `expires_in` | Number | How long the cached output lives, in seconds. Defaults to 60 seconds. |
| `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`. |
| `force` | Boolean | Set `true` to skip the existing entry and rewrite it. Useful while developing. |
| `skip_nil` | Boolean | Set `true` to leave the cache untouched when the block produces nothing. |

## How the cache key is built

The final key combines the name you pass, the current store, and each entry in `items`. Two things follow from this:

- **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.
- **`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.
- **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.

Changing 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.

:::note
Caching 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).
:::

## Examples

### Cache a product card

Pass the product so each one is cached separately, and the store and customer so that price, availability, and customer-specific text stay correct:


```liquid

{%- cache "product", items: [product, current_store, current_customer] -%}
  <h3>{{ product.name }}</h3>
  {{ product.pricing.price | money }}
  {%- render "products/product/add_to_cart", product: product -%}
{%- endcache -%}
```


### Cache the header

The 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:


```liquid

{%- cache "header", items: [current_store, current_customer, current_cart] -%}
  {%- render "menu" -%}
  <span class="SC-Cart_count">{{ current_cart.item_count }}</span>
{%- endcache -%}
```


### Build the key from a variable, and hold it longer

Where 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:


```liquid

{%- capture related_key %}related/{{ product.id }}{% endcapture -%}
{%- cache related_key, items: [current_customer], expires_in: 600 -%}
  {%- for related in product.related_products -%}
    {%- render "products/card", product: related -%}
  {%- endfor -%}
{%- endcache -%}
```


### Cache an external call

Wrap an [`api` block](api-tag-reference) so the storefront does not call the service on every render:


```liquid

{%- cache "forecast", items: [current_store], expires_in: 900 -%}
  {% api url: 'https://api.example.com/v1/forecast', method: 'GET', async: false %}
    {% if response.status == 200 %}{{ response.body.summary }}{% endif %}
  {% endapi %}
{%- endcache -%}
```


## Additional recommendations

- **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.
- **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.
- **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.
- **Measure before and after.** Wrap the block in a [`timer` block](timer-tag-reference) to confirm it is worth caching.
- **Keep expiry short while developing,** or pass `force: true`, so you are not debugging output that a cached copy is producing.

:::warning
Prices, 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.
:::

---

## Follow StoreConnect

- [Email Newsletter](https://getstoreconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://getstoreconnect.com/partners)
- [News](https://getstoreconnect.com/articles/news)
- [Events](https://getstoreconnect.com/articles/events)
- [Feature Comparison](https://getstoreconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://getstoreconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [AI agents](https://support.storeconnect.com/ai)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

---

StoreConnect Support — https://support.storeconnect.com/articles/cache-tag-reference