---
title: "Liquid state and persistence"
source: https://support.storeconnect.com/articles/liquid-state-and-persistence
type: article
format: markdown
site: StoreConnect Support — product and developer documentation for StoreConnect
site_index: https://storeconnect.com/llms.txt
docs_index: https://support.storeconnect.com/llms.txt
note: Append .md to any page or article URL on this site to get its Markdown form.
---
# Liquid state and persistence

When building Liquid templates, you need to decide where to store data: in the template's local scope, in a component's isolated context, across pages in a visitor's session, or passed via parameters. This guide explains when to use each approach and shows practical examples for common scenarios.

## Storage mechanisms at a glance

| Mechanism | Scope | Lifetime | Visibility | Use Case |
|-----------|-------|----------|-----------|----------|
| **Local assigns** | Single template | One request | Nowhere (calc only) | Temporary calculations, loop variables |
| **Default parameters** | Single snippet | One request | Within snippet only | Optional parameters with fallbacks |
| **URL parameters** | One request | Single click | In `current_request.params` | Filters, sorting, pagination links |
| **Context variables** | Single component | Component lifecycle | Component only | Component state, interaction tracking |
| **Session variables** | Visitor, entire store | Session duration | All pages, all requests | Preferences, history, lightweight state |

## Local assigns — temporary template calculations

Use `assign` or `capture` to create variables that exist only within a single template for calculations or temporary storage.


```liquid

{% assign product_count = collection.products | size %}
{% assign total_price = 0 %}
{% for item in cart.items %}
  {% assign total_price = total_price | plus: item.price %}
{% endfor %}

<p>Cart total: {{ total_price | money }}</p>
```


**When to use:**
- Calculations (totals, counts, derived values)
- Loop variables and counters
- Conditional logic dependencies
- Values that only this template needs

**When NOT to use:**
- Cross-page state (won't be available on the next page)
- Visitor preferences (lost on page load)
- Anything that needs to persist

## Default parameters in snippets

Use the `default` tag at the top of a snippet to provide fallback values for parameters not passed by the caller. This creates self-documenting, reusable snippets.


```liquid

{# In snippets/product-card.liquid #}
{% default title: "Untitled" %}
{% default show_price: false %}
{% default currency: "USD" %}

<div class="card">
  <h3>{{ title }}</h3>
  {% if show_price %}<p>{{ price | money }}</p>{% endif %}
</div>
```


**When to use:**
- Defining optional parameters for a snippet
- Providing sensible defaults for reusable components
- Making parameters optional instead of required
- Self-documenting code (defaults show expected parameters)

**When NOT to use:**
- Overriding values passed by the caller (don't do this)
- Creating global state (defaults are local to the snippet)
- One-time calculations (use `assign` instead)

## URL parameters — request-scoped filtering

Use `current_request.params` to read query string parameters from the current URL. These parameters appear only in the current request and do not persist to the next page.


```liquid

{# Current URL: /products?category=shoes&sort=price #}

{% assign category = current_request.params.category | default: "all" %}
{% assign sort = current_request.params.sort | default: "popularity" %}

<h1>{{ category | capitalize }} (sorted by {{ sort }})</h1>

{% if category == "shoes" %}
  {# Show shoe products #}
{% endif %}
```


**When to use:**
- Filtering products by category or tag
- Sorting results (price, popularity, newest)
- Pagination links and page numbers
- One-time query strings that don't need to persist

**When NOT to use:**
- Storing sensitive data in URLs (visible in browser history)
- Anything that should persist after navigation (use session instead)
- Relying on parameters to be present (always use defaults)

**Security note:** Never trust URL parameters without validation. Treat them as untrusted user input.

## Context variables — component-local state

Use the `context` tag within a component template to store state that is local to that component and does not appear on other components or in the parent template.


```liquid

{# In a component template #}
{% context is_expanded: false %}
{% context selected_tab: "overview" %}

<button type="button" data-toggle="panel">
  {% if context.is_expanded %}Show Less{% else %}Show More{% endif %}
</button>

{% if context.is_expanded %}
  <div class="expanded-content">
    {# Component content here #}
  </div>
{% endif %}
```


**When to use:**
- Component interaction state (expanded/collapsed, selected tab)
- Component-local UI state (hover state, focus tracking)
- Temporary state within a single component
- Avoiding variable name collisions between components

**When NOT to use:**
- State that needs to be visible outside the component (use session)
- Persisting state across page requests (use session)
- Parent-child communication (use render parameters)
- Sensitive data (context values are sent to the browser)

**Lifetime:** Context variables exist for the lifetime of the component. They reset if the component re-renders unless you use JavaScript to persist the state.

## Session variables — visitor state across pages

Use the `session` tag to store data that persists for a visitor across multiple page requests. A session is tied to a browser and lasts until the session expires or the browser is closed.


```liquid

{# On the product page #}
{% session last_viewed_product: product.id %}
{% session last_viewed_product_name: product.name %}
```



```liquid

{# On the home page #}
{% if session_variables["last_viewed_product"] %}
  <p>You last viewed: {{ session_variables["last_viewed_product_name"] }}</p>
{% endif %}
```


**When to use:**
- Visitor preferences (items per page, view preference, sort order)
- Recently viewed items or history
- Temporary filters or search state
- Lightweight visitor state that needs to cross pages

**When NOT to use:**
- Sensitive data (passwords, payment info, customer IDs)
- Data that should persist beyond the session (use Salesforce)
- Large amounts of data (sessions have size limits)
- Anything regulated or privacy-sensitive

**Security:** Session variables are NOT encrypted. They are readable in cookies and visible in network requests. **Only store non-sensitive, visitor-preference data.**

### Session security rules

**Never store:**
- Passwords or authentication tokens
- Credit card numbers or payment information
- Social security numbers or government IDs
- Personal health information
- Personal identifiable information (PII)
- API keys or secrets
- Any data covered by privacy law (GDPR, CCPA, etc.)

**OK to store:**
- Color or theme preference
- Grid vs. list view preference
- Items per page preference
- Sort order preference
- Last viewed product ID
- Selected filter values
- Non-sensitive form state

## Form state — validation and dynamic fields

When building forms, you often need to store validation errors, field state, or dynamic field data. Here's how to choose where to store form state:

### Single-page forms with validation

For forms on a single page that validate and show errors, store state in local assigns or the form object:


```liquid

{% form custom_form_name, class: "product-form" %}
  {% assign title = form.fields["title"] %}
  <input type="text" name="{{ title.name }}" value="{{ title.value }}">
  {% if title.errors != blank %}
    {% for message in title.errors.messages %}
      <span class="error">{{ message }}</span>
    {% endfor %}
  {% endif %}

  <button type="submit">Save</button>
{% endform %}
```


### Multi-step forms with page submissions

For multi-step forms that submit to the server and reload the page, use session variables to preserve entered data across steps:


```liquid

{# Step 1: Collect email #}
{% if current_request.params.step == "1" %}
  {% session form_email: current_request.params.email %}
{% endif %}

{# Step 2: Collect address #}
{% if current_request.params.step == "2" %}
  <p>Email: {{ session_variables["form_email"] }}</p>
  <input type="email" name="address">
{% endif %}
```


### Dynamic fields within a component

For dynamic fields that appear/disappear based on user interaction, use context variables:


```liquid

{# In a component #}
{% context show_advanced_options: false %}

<button data-toggle="advanced">Show More Options</button>

{% if context.show_advanced_options %}
  <input name="advanced_field_1">
  <input name="advanced_field_2">
{% endif %}
```


## URL parameters vs. session vs. context

Choose based on who needs to see the value and for how long:

### Use URL parameters when:
- Filtering or sorting products (`?category=shoes&sort=price`)
- Navigating to a specific page or section
- The state can be bookmarked or shared in a link
- The state is temporary (just this request)

**Example:** `/products?color=blue&size=large&page=2`

### Use session when:
- The visitor has a preference they want to remember
- The preference should carry across the entire store
- The state is lightweight and non-sensitive
- The state should persist while they shop

**Example:** Visitor selected "Show 20 per page" and that preference applies to all collection pages they visit.

### Use context when:
- The state is local to a single component
- The state does not affect other components
- The state is temporary (component lifecycle only)
- The state is used for interaction tracking

**Example:** An accordion panel is open or closed; other panels don't know or care about this state.

## Common patterns and examples

### Pattern 1: Remember collection view preference

Visitor chooses grid vs. list view and wants that preference to stick:


```liquid

{# When visitor clicks a view preference button #}
{% if current_request.params.set_view %}
  {% session collection_view: current_request.params.set_view %}
{% endif %}

{# Get the stored preference or default to grid #}
{% assign view = session_variables["collection_view"] | default: "grid" %}

<div class="view-options">
  <button {% if view == "grid" %}active{% endif %}>Grid</button>
  <button {% if view == "list" %}active{% endif %}>List</button>
</div>

{% if view == "grid" %}
  <div class="grid">
    {# Grid layout #}
  </div>
{% else %}
  <div class="list">
    {# List layout #}
  </div>
{% endif %}
```


### Pattern 2: Track filter selections on a collection

Visitor applies filters and you want to remember them while they browse, but clear them on next session:


```liquid

{# When visitor applies filters #}
{% if current_request.params.apply_filters %}
  {% session active_filters: current_request.params.filters %}
{% endif %}

{# Retrieve filters or start with empty #}
{% assign filters = session_variables["active_filters"] | default: empty_array %}

{# Display active filters #}
{% for filter in filters %}
  <span class="active-filter">{{ filter }} <a href="?clear=true">×</a></span>
{% endfor %}
```


### Pattern 3: Multi-step checkout with session persistence

Guide the customer through checkout steps, preserving entered data:


```liquid

{% assign current_step = current_request.params.step | default: "1" %}

{% case current_step %}
  {% when "1" %}
    {# Step 1: Shipping address #}
    {% if current_request.method == "POST" %}
      {% session checkout_email: current_request.params.email %}
      {% session checkout_address: current_request.params.address %}
    {% endif %}
    <form method="post">
      <input type="email" name="email" value="{{ session_variables["checkout_email"] | default: "" }}">
      <input type="text" name="address" value="{{ session_variables["checkout_address"] | default: "" }}">
      <button type="submit">Next</button>
    </form>

  {% when "2" %}
    {# Step 2: Payment — show previously entered email #}
    <p>Shipping to: {{ session_variables["checkout_email"] }}</p>
    <p>Address: {{ session_variables["checkout_address"] }}</p>
    <form method="post">
      <input type="text" name="card_number" placeholder="Card number">
      <button type="submit">Complete Order</button>
    </form>
{% endcase %}
```


### Pattern 4: Component with toggle state

An expandable FAQ component where each item can be open or closed:


```liquid

{# In a component #}
{% context is_open: false %}

<div class="faq-item">
  <button class="question" data-toggle="answer">
    {{ title }}
    {% if context.is_open %}▼{% else %}▶{% endif %}
  </button>

  {% if context.is_open %}
    <div class="answer">
      {{ content }}
    </div>
  {% endif %}
</div>
```


### Pattern 5: Dashboard with multiple widget states

A dashboard where each widget can be expanded independently:


```liquid

{# In the dashboard template #}

{% for widget in widgets %}
  {# Each widget is a component with its own context #}
  {% render "widgets/card", widget: widget %}
{% endfor %}
```



```liquid

{# In components/widgets/card.liquid #}
{% context is_expanded: false %}

<div class="widget-card">
  <div class="widget-header">
    <h3>{{ widget.title }}</h3>
    <button class="expand-btn" data-toggle="expand">
      {% if context.is_expanded %}−{% else %}+{% endif %}
    </button>
  </div>

  {% if context.is_expanded %}
    <div class="widget-content">
      {{ widget.content }}
    </div>
  {% endif %}
</div>
```


## Security considerations

### Session variables: minimal, non-sensitive only

Session variables are accessible in:
- Browser cookies
- Network requests
- Browser developer tools
- Any JavaScript on the page

**Never store sensitive data** like passwords, payment info, or personal IDs. Store only display preferences and non-sensitive history.

### Context variables: non-sensitive, minimal only

Context variables are:
- Sent to the browser as part of component state
- Visible in network requests and browser inspector
- Limited in size (don't store large objects)

Keep context variables minimal and non-sensitive. Use for UI state and interaction tracking only.

### URL parameters: untrusted input

Always validate and sanitize URL parameters:


```liquid

{# Unsafe #}
<p>{{ current_request.params.user_message }}</p>

{# Safer: use filters and validate #}
<p>{{ current_request.params.user_message | strip_html | escape }}</p>
```


### Local assigns and defaults: safe (not exposed)

Local variables and defaults are never exposed outside the template. They are safe for any data type, including sensitive information.

## Execution timeline

Understanding when each mechanism is evaluated helps you choose the right one:

1. **URL load** — Browser navigates to a URL
2. **Controller executes** — Server processes the request, populates `current_request.params`
3. **Template renders** — Liquid template runs, creating local assigns, reading/writing session
4. **HTML sent** — Page HTML (including context data) sent to browser
5. **Page displayed** — Browser renders HTML
6. **User interaction** — If component, context may update; if redirect, go to step 1

## Decision tree

**I need to store data — where should I put it?**

- Is it needed only once, for a calculation in this template?
  → **Use `assign` or `capture`**

- Is it an optional parameter to a snippet?
  → **Use `default` tag at the top of the snippet**

- Is it from the current URL (filters, sorting, pagination)?
  → **Use `current_request.params`**

- Is it state within a single component?
  → **Use `context`**

- Is it a visitor preference or history that should persist across pages?
  → **Use `session` (non-sensitive only)**

- Is it sensitive data that should never leave the server?
  → **Store in Salesforce, never in client-side storage**

## Related articles and references

- **[Session tag reference](session-tag-reference)** — detailed session tag syntax and examples
- **[Context tag reference](context-tag-reference)** — detailed context tag syntax and examples
- **[Default tag reference](default-tag-reference)** — detailed default tag syntax and examples
- **[Render tag reference](render-tag-reference)** — how to pass parameters to snippets
- **[Component tag reference](component-tag-reference)** — rendering dynamic components
- **[Liquid session variables](liquid-session-variables)** — comprehensive session variable guide

---

## Follow StoreConnect

- [Email Newsletter](https://storeconnect.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://storeconnect.com/partners)
- [Become a Partner](https://storeconnect.com/become-a-partner)
- [News](https://storeconnect.com/articles/news)
- [Events](https://storeconnect.com/articles/events)
- [Live Events](https://storeconnect.com/live-events)
- [Feature Comparison](https://storeconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://storeconnect.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

## Machine-readable

- [Site index for agents](https://storeconnect.com/llms.txt): curated map of the StoreConnect site in llms.txt format
- [Documentation index for agents](https://support.storeconnect.com/llms.txt): full technical and product documentation map

Every page and article on this site has a Markdown rendering: append `.md` to its URL.

Continue in Markdown: [Help documentation](https://support.storeconnect.com/help-documentation.md) · [Developer reference](https://support.storeconnect.com/developer-reference.md) · [Videos & tutorials](https://support.storeconnect.com/videos-tutorials.md) · [Release notes](https://support.storeconnect.com/release-notes.md)

---

StoreConnect Support — https://support.storeconnect.com/articles/liquid-state-and-persistence