Skip to content
Log in

Theme forms

On this page

Forms are the primary interaction mechanism in StoreConnect. The {% form %} tag generates HTML forms with the correct action URL, CSRF protection, and field definitions, so you never need to write a raw <form> element for user interactions.

The form tag

Every user interaction in a theme goes through {% form %}. It builds the <form> element with the right action and method, includes the CSRF token, and exposes a form drop for the fields and errors.

```liquid

{% form “form-type”, id: “form-id”, class: “form-class” %} <label for=”{{ form.fields[“field_name”].id }}”>Field label</label> <input id=”{{ form.fields[“field_name”].id }}” name=”{{ form.fields[“field_name”].name }}” value=”{{ form.fields[“field_name”].value }}”> {% endform %} ```

This article covers what is specific to building a theme around forms: the HTML you get, CSRF in a theme layout, and extending a standard form with your own parameters. For the tag itself, see the form tag reference.

HTML attributes on forms

Any option that is not consumed by the form type itself becomes an HTML attribute on the generated <form> element. This includes id, class, and data-* attributes:

```liquid

{% form “add-to-cart”, product_id: product.id, class: “SC-ProductCard_action”, id: “add-to-cart-form”, data-cart-form: true %} … {% endform %} ```

Generates:

```html

...

```

Common HTML options:

Option Example Purpose
class class: "SC-Panel" CSS class on the form
id id: "checkout-form" HTML id attribute
data-* data-cart-form: true Custom data attributes for JavaScript hooks

Reserved options (consumed internally, not passed to HTML): url, method, format, scope, model, authenticity_token, local, builder, data, html, remote, data-remote.

Form-specific options (consumed by the form type): For example, product_id for add-to-cart, provider for payment forms, custom_form for custom forms. These are extracted by the form handler and do not appear as HTML attributes.

The form drop and its errors

The form drop gives you fields, errors and path, and nothing else: a field is always reached as form.fields["name"], never as form.name. The form tag reference documents the drop, the field attributes and the shape of a FormError, and the Liquid forms guide covers displaying errors on the page.

CSRF protection

All forms require a CSRF token. The {% form %} tag includes it automatically as a hidden authenticity_token field. Your layout must include {{ csrf_meta_tags }} in the <head> for AJAX requests:

```javascript

const token = document.querySelector(‘meta[name=”csrf-token”]’).content;

fetch(‘/cart/items’, { method: ‘POST’, headers: { ‘X-CSRF-Token’: token, ‘Content-Type’: ‘application/x-www-form-urlencoded’ }, body: new URLSearchParams({ product_id: ‘123’, quantity: ‘1’ }) }); ```

:::warning Never write a raw <form> element for user interactions. Without the {% form %} tag, the CSRF token is missing and all submissions will fail with a security error. :::

Form submission flow

A submission is validated server-side, then either redirects on success or re-renders the page with form.errors populated and the customer’s values preserved. The Liquid forms guide walks through that cycle and what it means for your markup.

Form types and worked patterns

The Liquid forms reference lists every form type with its fields and parameters, and the Liquid forms guide has worked patterns for the common ones, including add to cart, login, contact and CMS custom forms.

Advanced: extending forms with custom parameters

You can add any extra <input> elements inside a {% form %} block. The standard form handler ignores unrecognized fields, but they are still submitted and available via current_request.params in Liquid controllers. This opens up powerful patterns for custom logic.

How it works

  1. Add custom hidden inputs or visible fields inside any {% form %} block.
  2. The form submits them alongside the standard fields.
  3. The platform’s built-in form handler processes the standard fields and ignores the extras.
  4. A Liquid controller (before / after / final) can read the extras via current_request.params.

Example: post-action data capture

Capture a gift message from the add-to-cart form and save it to the cart:

```liquid

{% form “add-to-cart”, product: current_product %} <input type=”number” name=”{{ form.fields[“quantity”].name }}” value=”1” min=”1”>

{% endform %} ```

In a Liquid controller (controllers/carts/add.liquid):

```liquid

{% after %} {%- assign params = current_request.params -%} {% if params.gift_message != blank %} {% update current_cart, field: “gift_message__c”, value: params.gift_message %} {% endif %} {% endafter %} ```

Example: client-side validation with server redirect

JavaScript sets a hidden input to flag an invalid state. The before controller checks it and redirects with an error before the form is processed:

```liquid

{% form “checkout-shipping-information” %} {% render “checkout/shipping_information/form”, form: form %} {% endform %} ```

In controllers/checkout/steps/shipping/update.liquid:

```liquid

{% before %} {%- assign params = current_request.params -%} {% if params.js_validation_failed != blank %} {% redirect to: “/checkout/shipping_information”, alert: “Please enter a valid shipping address” %} {% endif %} {% endbefore %} ```

Key points for custom parameters

  • Any input name works — the platform ignores fields it does not recognize, so extra inputs are safe.
  • current_request.params contains all submitted form data as a Map, including your custom fields.
  • before runs first — use it to validate or redirect before the standard action.
  • after runs second — use it to persist extra data after the standard action succeeds.
  • {% redirect %} stops execution — once a redirect is issued in before, the standard action and after are skipped.

The {% redirect %} tag

```liquid

{% redirect to: “/path” %} {% redirect to: “/path”, notice: “Operation completed” %} {% redirect to: “/path”, alert: “Something went wrong” %} {% redirect to: “/path”, status: 301 %} ```

Option Description
to URL path to redirect to (required)
notice Flash notice message (informational)
alert Flash alert message (error)
status HTTP status code (default: 302)

Was this article helpful?

Was this article helpful?