---
title: "Liquid controller actions guide"
source: https://support.storeconnect.com/articles/liquid-controller-actions
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 controller actions guide

A Liquid controller handles HTTP requests to your storefront. This guide covers extracting parameters, validating input, executing actions (cart operations, shipping, promotions), and controlling the response, all without writing Apex.

## Before you start

- Controller templates live at `controllers/<controller>/<action>.liquid`
- Controllers run in three phases: `{% before %}`, **standard action**, then `{% after %}`
- All controller tags work only inside a controller template; they are silent no-ops elsewhere
- Read [Liquid controller lifecycle](liquid-controllers-guide) for the full execution model

## Request parameter flow

The typical controller flow:

1. **Extract parameters** — read URL and form input with `{% params %}`
2. **Validate** — check that inputs are safe and sensible
3. **Execute action** — use `{% action %}` to run built-in operations (add to cart, etc.)
4. **Respond or redirect** — send a response or redirect the browser

## Extracting parameters

Use `{% params %}` in the `{% before %}` phase to extract query strings and form POST parameters.

### Basic parameter extraction


```liquid

{% before %}
  {% params product_id: current_request.params.product_id, quantity: current_request.params.quantity %}
{% endbefore %}
```


After this tag, `params.product_id` and `params.quantity` are available to use in the controller.

### With defaults

Use the Liquid `default` filter to provide fallback values:


```liquid

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


### Converting types

Parameters arrive as strings. Convert them as needed:


```liquid

{% before %}
  {% params count: current_request.params.count | default: 1 | times: 1 %}
  {%- comment -%} times: 1 converts string to number {%- endcomment -%}
{% endbefore %}
```


## Validating input

Always validate parameters before using them. Invalid input is a security risk and can cause confusing errors downstream.

### Check for presence


```liquid

{% before %}
  {% assign product_id = current_request.params.product_id %}

  {% params product_id: product_id %}

  {% if product_id == blank %}
    {% respond body: '{"error": "product_id is required"}', status: 400, layout: false %}
  {% endif %}
{% endbefore %}
```


### Check length or range


```liquid

{% before %}
  {% assign quantity = current_request.params.quantity | default: 1 | times: 1 %}

  {% params quantity: quantity %}

  {% if quantity < 1 or quantity > 100 %}
    {% redirect to: current_store.product_path, alert: "Quantity must be between 1 and 100" %}
  {% endif %}
{% endbefore %}
```


### Check allowed values


```liquid

{% before %}
  {% assign view_mode = current_request.params.mode %}

  {% params view_mode: view_mode %}

  {% if view_mode != "grid" and view_mode != "list" %}
    {% redirect to: current_store.products_path %}
  {% endif %}
{% endbefore %}
```


## Setting variables for rendering

Use `{% variables %}` to make data available to the page template and all snippets.


```liquid

{% before %}
  {% variables page_title: "My Custom Title", show_sidebar: true %}
{% endbefore %}
```


Inside the page template, reference these as top-level variables:


```liquid

<h1>{{ page_title }}</h1>
{% if show_sidebar %}
  {% render "sidebar" %}
{% endif %}
```


This is useful for:
- Setting page titles dynamically
- Controlling which sections render
- Passing controller context to snippets

## Executing built-in actions

Use `{% action %}` to execute pre-built controller operations. Available actions:

| Action | Purpose | Parameters |
|--------|---------|------------|
| `cart.add` | Add item to cart | `product_identifier`, `quantity` |
| `cart.update` | Update item quantity | `cart_item`, `quantity` |
| `cart.remove` | Remove item from cart | `cart_item` |
| `cart.empty` | Clear entire cart | (none) |
| `cart.select` | Select a specific cart | `cart` |
| `cart.create` | Create a new cart | (none) |
| `cart.clone` | Duplicate a cart | `cart` |
| `shipping.set` | Set shipping method | `shipping_method` |
| `pricebook.set` | Set customer price book | `pricebook` |
| `pricebook.clear` | Clear custom price book | (none) |
| `outlet.set` | Set fulfillment outlet | `outlet` |
| `outlet.clear` | Clear outlet selection | (none) |
| `promotion.apply` | Apply promotion code | `code` |
| `promotion.remove` | Remove a promotion | `promotion` |
| `promotion.clear` | Clear all promotions | (none) |

### Example: Add to cart


```liquid

{% before %}
  {% assign product_id = current_request.params.product_id %}
  {% assign quantity = current_request.params.quantity | default: 1 | times: 1 %}

  {% params product_id: product_id, quantity: quantity %}

  {% if product_id == blank or quantity < 1 %}
    {% respond body: '{"error": "invalid input"}', status: 400, layout: false %}
  {% endif %}
  
  {% action "cart.add", product_identifier: product_id, quantity: quantity %}
{% endbefore %}

{% after %}
  {% redirect to: current_store.cart_path, notice: "Item added to cart" %}
{% endafter %}
```


### Example: Apply promotion


```liquid

{% before %}
  {% assign code = current_request.params.promo_code %}

  {% params code: code %}

  {% if code != blank %}
    {% action "promotion.apply", code: code %}
  {% endif %}
{% endbefore %}

{% after %}
  {%- new Map result -%}
  {%- assign result = result | set_key: "cart_total", current_cart.totals.subtotal -%}
  {%- assign result = result | set_key: "promotion_count", current_cart.promotions.size -%}
  {% respond body: result | json, status: 200, layout: false %}
{% endafter %}
```


## Sending responses

Use `{% respond %}` to send a custom response (instead of rendering the page template).

### HTML response


```liquid

{% respond body: "<h1>Success!</h1>", status: 200 %}
```


### JSON response


```liquid

{% before %}
  {% params product_id: current_request.params.product_id %}
{% endbefore %}

{% after %}
  {%- new Map result -%}
  {%- assign result = result | set_key: "success", true -%}
  {%- assign result = result | set_key: "product_id", current_request.params.product_id -%}
  {%- assign response_body = result | json -%}
  {% respond body: response_body, status: 200, layout: false %}
{% endafter %}
```


**Key options:**
- `body:` — the response content (string)
- `status:` — HTTP status code. Defaults to `302`, so always set it explicitly when returning a body
- `layout:` — include theme layout? (default true; use false for JSON/API responses)
- `notice:` — flash message (positive)
- `alert:` — flash message (negative)

### Error response


```liquid

{% before %}
  {% assign quantity = current_request.params.quantity | default: 1 | times: 1 %}

  {% params quantity: quantity %}

  {% if quantity > 50 %}
    {%- new Map error -%}
    {%- assign error = error | set_key: "error", "Quantity cannot exceed 50" -%}
    {% respond body: error | json, status: 422, layout: false %}
  {% endif %}
{% endbefore %}
```


## Redirects

Use `{% redirect %}` to send the browser to a different URL.

### Simple redirect


```liquid

{% after %}
  {% redirect to: current_store.cart_path %}
{% endafter %}
```


### Redirect with flash message


```liquid

{% after %}
  {% redirect to: current_store.account_path, notice: "Changes saved successfully" %}
{% endafter %}
```


### Conditional redirect


```liquid

{% before %}
  {% params customer_id: current_request.params.customer_id %}
{% endbefore %}

{% after %}
  {% if current_request.params.customer_id == blank %}
    {% redirect to: current_store.account_login_path, alert: "Please log in first" %}
  {% else %}
    {% redirect to: current_store.account_path %}
  {% endif %}
{% endafter %}
```


### Difference from respond

- **`{% redirect %}`** — sends a 302 redirect; the browser fetches a new page
- **`{% respond %}`** — returns content directly; the page does not change in the browser's address bar

Use redirects for state changes (after adding to cart, updating profile). Use responses for API endpoints that return data.

## Complete examples

### Add-to-cart form handler


```liquid

{% before %}
  {%- comment -%}
    Extract and validate input
  {%- endcomment -%}
  {% assign product_id = current_request.params.product_id %}
  {% assign quantity = current_request.params.quantity | default: 1 | times: 1 %}

  {% params product_id: product_id, quantity: quantity %}

  {% if product_id == blank %}
    {% redirect to: current_store.products_path, alert: "Product not found" %}
  {% endif %}
  
  {% if quantity < 1 or quantity > 100 %}
    {% redirect to: current_store.product_path, alert: "Invalid quantity" %}
  {% endif %}
  
  {%- comment -%}
    Execute the action
  {%- endcomment -%}
  {% action "cart.add", product_identifier: product_id, quantity: quantity %}
{% endbefore %}

{%- comment -%}
  Redirect after success
{%- endcomment -%}
{% after %}
  {% redirect to: current_store.cart_path, notice: "Item added to cart" %}
{% endafter %}
```


### AJAX endpoint for cart status


```liquid

{% before %}
  {%- comment -%}
    No parameters needed; just return cart state
  {%- endcomment -%}
{% endbefore %}

{% after %}
  {%- new Map response -%}
  {%- assign response = response | set_key: "item_count", current_cart.items.size -%}
  {%- assign response = response | set_key: "subtotal", current_cart.totals.subtotal -%}
  {%- assign response = response | set_key: "currency", current_store.currency -%}
  {% respond body: response | json, status: 200, layout: false %}
{% endafter %}
```


### Promotion code validator


```liquid

{% before %}
  {% assign code = current_request.params.code %}

  {% params code: code %}

  {% if code == blank %}
    {% respond body: '{"valid": false, "message": "Code required"}', status: 400, layout: false %}
  {% endif %}
  
  {% action "promotion.apply", code: code %}
{% endbefore %}

{% after %}
  {%- new Map result -%}
  {%- assign promos_applied = current_cart.promotions.size -%}
  {%- if promos_applied > 0 -%}
    {%- assign result = result | set_key: "valid", true -%}
    {%- assign result = result | set_key: "discount", current_cart.totals.discount_amount -%}
  {%- else -%}
    {%- assign result = result | set_key: "valid", false -%}
    {%- assign result = result | set_key: "message", "Promotion code not found or expired" -%}
  {%- endif -%}
  {% respond body: result | json, status: 200, layout: false %}
{% endafter %}
```


## Common patterns

### Guard clause for authentication


```liquid

{% before %}
  {% if current_customer == blank %}
    {% redirect to: current_store.account_login_path %}
  {% endif %}
{% endbefore %}
```


### Type conversion


```liquid

{% before %}
  {% params id: current_request.params.id | times: 1 %}
  {%- comment -%} string → number {%- endcomment -%}
{% endbefore %}
```


### Default values


```liquid

{% before %}
  {% params sort: current_request.params.sort | default: "name" %}
  {% params limit: current_request.params.limit | default: 20 | times: 1 %}
{% endbefore %}
```


### Whitelist allowed values


```liquid

{% before %}
  {% params mode: current_request.params.mode %}
  
  {% unless mode == "grid" or mode == "list" %}
    {% assign mode = "grid" %}
  {% endunless %}
  
  {% variables view_mode: mode %}
{% endbefore %}
```


## Security considerations

- **Always validate** — treat all user input (query strings, form posts, request headers) as untrusted
- **Use built-in actions** — don't construct custom business logic if a built-in action exists
- **Scope to customer** — when reading/writing customer data, filter by `current_customer`
- **Never expose IDs in responses** — don't leak record IDs, counts, or internal structure to the client unless intentional
- **Use HTTPS only** — controllers over HTTP expose parameters in logs and network traces

---

## 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-controller-actions