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

```liquid

{% form “form-type” [, option: value, id: “form-id”, class: “form-class”] %} {{ form.field_name.label }} {% endform %} ```

The {% form %} tag automatically: 1. Creates a <form> HTML element with the correct action URL and method. 2. Includes a hidden authenticity_token field for CSRF protection. 3. Makes a form drop available inside the block with field definitions and errors. 4. Passes through extra options as HTML attributes on the <form> element.

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

Inside a {% form %} block, the form variable provides:

Property Type Description
form.errors Array Validation error messages
form.[field_name] FieldDrop Access to individual form fields
form.[field_name].name String The input name attribute value
form.[field_name].value Any The current or default value
form.[field_name].label String Human-readable label
form.[field_name].id String HTML id attribute
form.[field_name].errors Array Field-specific errors

Error handling

After a failed form submission, the form is re-displayed with errors. The form.errors array contains error messages:

```liquid

{% form “login” %} {% render “form_errors”, errors: form.errors %}

<input type=”email” id=”email” name=”{{ form.username.name }}” value=”{{ form.username.value }}” {% if form.errors.size > 0 %}aria-invalid=”true”{% endif %} required>

{% endform %} ```

StoreConnect stashes form data on failed submissions so the page re-renders with fields pre-filled and form.errors populated. This happens automatically with {% form %}.

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

  1. User fills in the form and clicks submit.
  2. Browser sends a POST request with application/x-www-form-urlencoded data, including the authenticity_token.
  3. The platform verifies the CSRF token.
  4. The platform validates the data.
  5. On success: the platform performs the action and redirects, usually with a flash notice.
  6. On failure: the platform re-renders the page with the form pre-filled and form.errors populated.

Form types by category

Every form type StoreConnect registers, grouped the way the platform groups them. Use the name exactly as shown. {% form %} resolves the action URL, method, and CSRF token for you, so a theme never needs to know the endpoint.

Accounts

Form type Purpose
register Create an account
account Edit the signed-in account
account-missing-details Supply details missing from an account
forgot-password Request a password reset
reset-password Set a new password
resend-confirmation Resend the confirmation email
accept-invitation Accept an invitation to an account

Session

Form type Purpose
login Sign in
sso-login Sign in through single sign-on
single-sign-on Alias of sso-login; both resolve to the same form

Cart

Form type Purpose
add-to-cart Add a product to the cart
add-bundle-to-cart Add a configured bundle to the cart
add-preset-bundle Add a preset bundle to the cart
cart Update quantities or remove items

Checkout

Form type Purpose
checkout-customer-information Customer details step
checkout-shipping-information Shipping method step
checkout-accept-terms Terms acceptance step
checkout-set-password Set a password during checkout
payment Submit payment
payment-not-required Complete an order with nothing to pay
apply-promo-code Apply a promotion code
remove-promo-code Remove an applied promotion code
apply-voucher Apply a voucher
remove-voucher Remove an applied voucher
activate-voucher Activate a voucher
apply-account-credit Apply account credit
remove-account-credit Remove applied account credit

Payments and subscriptions

Form type Purpose
subscription-payment Pay a subscription
update-subscription-payment-details Change the card a subscription bills to
additional-payment-billing-address Billing address for an additional payment

Bookings

Form type Purpose
booking-attendee-add Add an attendee to a booking
booking-attendee-edit Edit a booking attendee

Privacy

Form type Purpose
privacy-settings Save cookie and privacy choices
privacy-accept-all Accept all cookie categories
privacy-reject-all Reject all optional cookie categories

Geolocation

Form type Purpose
geolocation-select Choose a location
geolocation-dismiss Dismiss the location prompt

Custom forms

Form type Purpose
custom-form Submit a Custom Form defined in Salesforce

Common form patterns

Add to cart

```liquid

{% form “add-to-cart”, product_id: product.id %}

{% endform %} ```

Login

```liquid

{% form “login”, class: “login-form” %} {% render “form_errors”, errors: form.errors %}

Forgot password?

Create account

{% endform %} ```

Contact form

There is no built-in contact form type. Build the form as a Custom Form in Salesforce, then render it with custom-form as shown below.

Custom form

Custom forms are defined in the CMS and rendered from a form object:

```liquid

{% form “custom-form”, custom_form: my_form %} {% for question in my_form.questions %} <div class="field"> {% case question.question_type %} {% when “text” %} {% when “text_area” %} {% when “picklist” %} {% when “date” %} {% endcase %} </div> {% endfor %} {% endform %} ```

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_id: current_product.id %}

{% 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?