# Liquid controllers guide

Source: https://support.storeconnect.com/articles/liquid-controllers-guide · Last modified 21 August 2026

Liquid controllers add server-side logic to your StoreConnect theme pages. They live in the `controllers/` directory of your theme and run in three ordered phases around the normal page request. This article explains those phases and the action tags you use inside them.

For a list of all controller routes (HTTP verbs and URL paths), see [Liquid controllers](liquid-controllers).

## How controllers work

When a page is requested, StoreConnect matches the URL to a controller and action. The system runs the corresponding controller template in three phases:

1. **`{% before %}`** — runs before the page renders. Use for data preparation, input validation, cart operations, and early redirects.
2. **`{% after %}`** — runs after the main action. Use for redirects based on results, custom JSON responses, and post-processing.
3. **`{% final %}`** — runs after the HTTP response is sent to the browser. Use for fire-and-forget work like analytics calls and logging. Any `{% api %}` call inside `{% final %}` is automatically asynchronous.

Not every controller needs all three phases — include only the ones you need.


```liquid

{% before %}
  {% params product_id: current_request.params.id %}
  {% variables page_title: "Product detail" %}
{% endbefore %}

{% after %}
  {% if some_condition %}
    {% redirect to: "/cart" %}
  {% endif %}
{% endafter %}

{% final %}
  {%- new Map payload -%}
  {%- assign payload = payload | set_key: "event", "page_view" -%}
  {% api url: "https://analytics.example.com/track", method: "post", data: payload %}
  {% endapi %}
{% endfinal %}
```


## Action tags

Action tags are only valid inside controller phase blocks. Using them outside a phase block has no effect.

### `{% params %}`

Reads values from the incoming request and makes them available as controller parameters for the current action.


```liquid

{% before %}
  {% params
    product_id: current_request.params.product_id,
    quantity: current_request.params.qty | default: 1
  %}
{% endbefore %}
```


### `{% variables %}`

Sets template variables that are accessible in the page template.


```liquid

{% before %}
  {% variables
    page_title: "Featured products",
    show_sidebar: true,
    max_items: 12
  %}
{% endbefore %}
```


### `{% redirect %}`

Redirects to a different URL and stops further controller execution. All subsequent phases are skipped.


```liquid

{% before %}
  {% unless current_customer %}
    {% redirect to: "/auth/sign_in", alert: "Please log in to continue" %}
  {% endunless %}
{% endbefore %}

{% after %}
  {% redirect to: "/cart", notice: "Item added to your cart" %}
{% endafter %}
```


| Option | Description |
|--------|-------------|
| `to` | URL path to redirect to (required) |
| `notice` | Flash notice message displayed after redirect |
| `alert` | Flash alert message displayed after redirect |
| `status` | HTTP status code (default: `302`) |

### `{% respond %}`

Sends a custom HTTP response and stops normal page rendering. Used for AJAX endpoints and API-style responses.


```liquid

{% after %}
  {%- new Map result -%}
  {%- assign result = result | set_key: "success", true | set_key: "count", current_cart.item_count -%}
  {%- assign body = result | json -%}
  {% respond status: 200, body: body %}
{% endafter %}
```


| Option | Description |
|--------|-------------|
| `status` | HTTP status code (required) |
| `body` | Response body as a string |
| `json` | Response body as a JSON object (alternative to `body`) |

### `{% update %}`

Updates a custom data field on a database object. The field name uses the platform's internal identifier format.


```liquid

{% before %}
  {%- assign new_count = current_product.data.view_count__c | plus: 1 -%}
  {% update current_product, field: "view_count__c", value: new_count %}
{% endbefore %}
```


### `{% action %}`

Executes a named platform action. Actions cover cart management, shipping, pricebooks, and promotions.

#### Cart actions

| Action | Parameters | Description |
|--------|-----------|-------------|
| `cart.add` | `product_identifier`, `quantity`, `variant_id` | Add a product to the cart |
| `cart.update` | `cart_item_id`, `quantity` | Update an item's quantity |
| `cart.remove` | `cart_item_id` | Remove an item from the cart |
| `cart.empty` | (none) | Clear all items from the cart |
| `cart.select` | `identifier` | Switch to a specific cart |
| `cart.clone` | `cart_id` | Clone an existing cart |

#### Shipping actions

| Action | Parameters | Description |
|--------|-----------|-------------|
| `shipping.set` | `address_id`, `method` | Set the shipping address and method |

#### Pricebook actions

| Action | Parameters | Description |
|--------|-----------|-------------|
| `pricebook.set` | `pricebook_id` | Activate a specific pricebook |
| `pricebook.clear` | (none) | Clear the active pricebook |

#### Promotion actions

| Action | Parameters | Description |
|--------|-----------|-------------|
| `promotion.apply` | `code` | Apply a promotion code |
| `promotion.remove` | `code` | Remove a promotion code |
| `promotion.clear` | (none) | Remove all promotions |

## Common patterns

### Add to cart


```liquid

{% before %}
  {% action "cart.add",
    product_identifier: current_request.params.product_id,
    quantity: current_request.params.quantity | default: 1,
    variant_id: current_request.params.variant_id
  %}
{% endbefore %}

{% after %}
  {% redirect to: "/cart", notice: "Added to cart" %}
{% endafter %}
```


### Apply a promotion code


```liquid

{% before %}
  {% action "promotion.apply", code: current_request.params.promo_code %}
{% endbefore %}

{% after %}
  {% redirect to: "/cart" %}
{% endafter %}
```


### Return JSON from a controller


```liquid

{% after %}
  {%- new Map response -%}
  {%- assign response = response | set_key: "cart_count", current_cart.item_count -%}
  {%- assign json_body = response | json -%}
  {% respond status: 200, body: json_body %}
{% endafter %}
```


### Async analytics in `{% final %}`

API calls inside `{% final %}` are always asynchronous — the response is sent to the browser before the call completes, so no `response` data is available.


```liquid

{% final %}
  {%- new Map event -%}
  {%- assign event = event | set_key: "type", "add_to_cart" | set_key: "product", current_product.id -%}
  {% api url: "https://analytics.example.com/events", method: "post", data: event %}
  {% endapi %}
{% endfinal %}
```


## Controller helpers

The `helpers/` directory contains shared Liquid logic for controllers. Include a helper with `{% render %}` inside a phase block:


```liquid

{% before %}
  {% render "helpers/delivery_options" %}
{% endbefore %}
```


Helpers work like snippets but are intended for controller-level logic rather than HTML output.

---

## 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/liquid-controllers-guide