Liquid controllers guide
On this page
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.
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:
{% before %}— runs before the page renders. Use for data preparation, input validation, cart operations, and early redirects.{% after %}— runs after the main action. Use for redirects based on results, custom JSON responses, and post-processing.{% 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.
Was this article helpful?
Thanks for your feedback! It helps us improve our docs.