> ## Documentation Index
> Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme Customer Accounts

> Render login, registration, order history, and address pages from your theme with templates/customers/*

# Theme Customer Accounts

Stores can render customer account pages (login, registration, order
history, addresses, password reset) directly from the active theme instead
of the built-in account pages. When the feature is on and the theme ships
the account templates, the storefront serves them inside the normal theme
layout — same header, footer, fonts, and settings as every other page.

## Enabling theme account pages

Merchants enable the feature in the admin under
**Settings → Preferences → Customer accounts** by choosing **Theme account
pages**.

The fallback is automatic and per-page:

* Feature **off** (default) → the built-in account pages render, exactly as
  before. Themes without account templates need no changes.
* Feature **on**, theme **ships** `templates/customers/*` → the theme's
  templates render.
* Feature **on**, theme **lacks** a given template → the storefront serves
  the built-in page for that route automatically. Nothing breaks.

<Note>
  Theme developers should ship the full template set (below) so merchants get
  a consistent experience when they switch the toggle on.
</Note>

## Template set

Account templates live under `templates/customers/` and follow the same
rules as every other template: they can be **JSON section templates**
(`.json`, composing sections) or **plain Aqua/Liquid templates**
(`.aqua` / `.liquid`). Either form is rendered inside the theme layout.

```
templates/customers/
├── login.aqua              (or login.json)
├── register.aqua
├── account.aqua
├── order.aqua
├── addresses.aqua
├── activate_account.aqua
└── reset_password.aqua
```

| Template                     | Renders                                              |
| ---------------------------- | ---------------------------------------------------- |
| `customers/login`            | Login form + password recovery form                  |
| `customers/register`         | Account registration                                 |
| `customers/account`          | Account dashboard: order history + addresses summary |
| `customers/order`            | Single order detail                                  |
| `customers/addresses`        | Address book (list, create, edit, delete)            |
| `customers/activate_account` | Set a password from an account-invite email link     |
| `customers/reset_password`   | Set a new password from a reset email link           |

## Routes

| Route                                     | Template                                  | Auth             |
| ----------------------------------------- | ----------------------------------------- | ---------------- |
| `/account`                                | `customers/account`                       | Required         |
| `/account/login`                          | `customers/login`                         | Guests only      |
| `/account/register`                       | `customers/register`                      | Guests only      |
| `/account/addresses`                      | `customers/addresses`                     | Required         |
| `/account/orders/{id}`                    | `customers/order`                         | Required         |
| `/account/activate/{customer_id}/{token}` | `customers/activate_account`              | Token from email |
| `/account/reset/{customer_id}/{token}`    | `customers/reset_password`                | Token from email |
| `/account/logout` (GET)                   | — logs the customer out, redirects to `/` | —                |
| `/account/recover` (POST)                 | — password-recovery submission target     | —                |

Redirect behavior is enforced by the platform — themes don't need their own
guards:

* A **logged-out** customer requesting `/account`, `/account/addresses`, or
  `/account/orders/{id}` is redirected to `/account/login`.
* A **logged-in** customer requesting `/account/login` or
  `/account/register` is redirected to `/account`.
* `GET /account/recover` redirects to `/account/login#recover` — themes
  conventionally use the `#recover` fragment to reveal the recovery form.

Use the `routes` object rather than hard-coding paths:
`routes.account_url`, `routes.account_login_url`,
`routes.account_logout_url`, `routes.account_register_url`,
`routes.account_addresses_url`, `routes.account_recover_url`.

## Forms

All account forms use the standard [`{% form %}` tag](/aqua/tags#form). The
tag renders the `<form>` element with the correct action URL and method and
injects any required hidden inputs — themes only supply the visible fields.

### customer\_login

Posts email + password. On failure the page re-renders with
`form.errors` set.

```aqua theme={null}
{% form 'customer_login' %}
  {{ form.errors | default_errors }}
  <input type="email" name="customer[email]" autocomplete="email" required>
  <input type="password" name="customer[password]" autocomplete="current-password" required>
  <button type="submit">{{ 'customer.login_page.sign_in' | t }}</button>
{% endform %}
```

### create\_customer

Registration. Posts to `/account`; on success the customer is logged in and
redirected to `/account`. On failure the register page re-renders with
`form.errors`, and the submitted `first_name`, `last_name`, and `email`
values are echoed back on the form object so inputs can keep their values.

```aqua theme={null}
{% form 'create_customer' %}
  {{ form.errors | default_errors }}
  <input type="text"  name="customer[first_name]" value="{{ form.first_name }}">
  <input type="text"  name="customer[last_name]"  value="{{ form.last_name }}">
  <input type="email" name="customer[email]"      value="{{ form.email }}" required>
  <input type="password" name="customer[password]" required>
  <button type="submit">{{ 'customer.register.submit' | t }}</button>
{% endform %}
```

### recover\_customer\_password

Password-recovery request. Takes a bare `email` input (no `customer[...]`
prefix). To prevent account enumeration, the platform **always reports
success** — `form.posted_successfully?` is `true` after any submission, so
show a neutral "check your email" message.

```aqua theme={null}
{% form 'recover_customer_password' %}
  {% if form.posted_successfully? %}
    <p>{{ 'customer.recover_password.success' | t }}</p>
  {% endif %}
  <input type="email" name="email" required>
  <button type="submit">{{ 'customer.recover_password.submit' | t }}</button>
{% endform %}
```

The reset email links the customer to
`/account/reset/{customer_id}/{token}`, which renders
`customers/reset_password`.

### reset\_customer\_password / activate\_customer\_password

Set a new password from a reset link, or a first password from an invite
link. Both take `customer[password]` and `customer[password_confirmation]`.
The identifying token from the URL is **injected automatically as hidden
inputs** by the `{% form %}` tag — themes add nothing.

```aqua theme={null}
{% form 'reset_customer_password' %}
  {{ form.errors | default_errors }}
  <input type="password" name="customer[password]" required>
  <input type="password" name="customer[password_confirmation]" required>
  <button type="submit">{{ 'customer.reset_password.submit' | t }}</button>
{% endform %}
```

On success the customer is logged in and redirected to `/account`.

### customer\_address

Create or edit an address. Pass `customer.new_address` to create, or an
existing address to edit — the form tag picks the right action URL either
way.

```aqua theme={null}
{% form 'customer_address', customer.new_address %}
  <input type="text" name="address[first_name]">
  <input type="text" name="address[last_name]">
  <input type="text" name="address[company]">
  <input type="text" name="address[address1]">
  <input type="text" name="address[address2]">
  <input type="text" name="address[city]">

  <select name="address[country]" data-default="{{ form.country }}">
    {{ all_country_option_tags }}
  </select>
  <select name="address[province]" data-default="{{ form.province }}"></select>

  <input type="text" name="address[zip]">
  <input type="tel"  name="address[phone]">

  <label>
    {{ form.set_as_default_checkbox }}
    {{ 'customer.addresses.set_default' | t }}
  </label>

  <button type="submit">{{ 'customer.addresses.add' | t }}</button>
{% endform %}
```

Inside a `customer_address` form:

| Property                                              | Description                                                                      |
| ----------------------------------------------------- | -------------------------------------------------------------------------------- |
| `form.id`                                             | The address id being edited (`new` for the create form) — useful for element ids |
| `form.set_as_default_checkbox`                        | Renders the "set as default address" checkbox input                              |
| `form.first_name`, `form.address1`, `form.country`, … | Current values of the address being edited                                       |

Notes:

* `address[country]` submits the full country **name** (that's what the
  option tags emit as values).
* `address[default]` = `1` marks the address as default — this is what
  `form.set_as_default_checkbox` renders.
* `customer_address` forms **never return field errors** — the submission
  always redirects back to `/account/addresses`, and
  `form.posted_successfully?` is always `true` for this form type.

**Deleting an address** is a POST to the address URL with a `_method=delete`
override. A plain form works:

```aqua theme={null}
<form method="post" action="/account/addresses/{{ address.id }}">
  <input type="hidden" name="_method" value="delete">
  <button type="submit"
          onclick="return confirm('{{ 'customer.addresses.delete_confirm' | t }}');">
    {{ 'customer.addresses.delete' | t }}
  </button>
</form>
```

Account pages also ship a small platform helper script that provides the
classic POST-link helper (synthesizes exactly this form + `_method`
submission from JavaScript) and the country/province selector — existing
theme JavaScript that constructs these helpers keeps working unmodified.
See [Helper JavaScript](#helper-javascript-on-account-pages).

### Form state: errors and success

| Property                              | Description                                                                  |
| ------------------------------------- | ---------------------------------------------------------------------------- |
| `form.errors`                         | Error codes from the last submission of **this** form type (empty otherwise) |
| `form.errors.messages[code]`          | Default English message for a code                                           |
| `form.errors.translated_fields[code]` | Human label for the field the code refers to                                 |
| `form.posted_successfully?`           | `true` when this form type was just submitted successfully                   |

`form.errors` is iterable (yields error codes such as `email`, `password`,
or the generic `form`) and supports `contains`:

```aqua theme={null}
{% if form.errors %}
  <ul>
    {% for code in form.errors %}
      <li>
        {% if code == 'form' %}
          {{ form.errors.messages[code] }}
        {% else %}
          {{ form.errors.translated_fields[code] }} —
          {{ form.errors.messages[code] }}
        {% endif %}
      </li>
    {% endfor %}
  </ul>
{% endif %}

{% if form.errors contains 'password' %}
  <p class="hint">{{ 'customer.register.password_hint' | t }}</p>
{% endif %}
```

For a one-liner, pipe through `default_errors` to render a ready-made
error list:

```aqua theme={null}
{{ form.errors | default_errors }}
```

A failed login sets the single generic `form` error code (with a default
message like "Incorrect email or password.") — no hint is given about
whether the email exists.

## Objects

### customer

Available on all account pages (and globally on every page when the
customer is logged in — `{% if customer %}` works in headers as usual).

| Property                                     | Type    | Description                                                              |
| -------------------------------------------- | ------- | ------------------------------------------------------------------------ |
| `customer.id`                                | string  | Customer ID                                                              |
| `customer.name`                              | string  | Full name                                                                |
| `customer.first_name` / `customer.last_name` | string  | Name parts                                                               |
| `customer.email`                             | string  | Email address                                                            |
| `customer.phone`                             | string  | Phone number                                                             |
| `customer.accepts_marketing`                 | boolean | Marketing opt-in                                                         |
| `customer.has_account`                       | boolean | Always `true` for logged-in customers                                    |
| `customer.orders`                            | array   | Order history (paginate with [`{% paginate %}`](#paginating-orders))     |
| `customer.orders_count`                      | number  | Total orders                                                             |
| `customer.total_spent`                       | number  | Lifetime spend, in the currency's **minor units** (pipe through `money`) |
| `customer.addresses`                         | array   | All saved [addresses](#address)                                          |
| `customer.addresses_count`                   | number  | Number of saved addresses                                                |
| `customer.default_address`                   | object  | The default [address](#address)                                          |
| `customer.new_address`                       | object  | Empty address stub for the create form                                   |
| `customer.tags`                              | array   | Customer tags                                                            |
| `customer.metafields`                        | object  | [Custom metafields](/aqua/metafields)                                    |

### order

Available as `order` on `customers/order`, and as each entry of
`customer.orders` on `customers/account`.

```aqua theme={null}
<h1>{{ 'customer.order.title' | t: name: order.name }}</h1>
<p>{{ order.created_at | date: '%B %d, %Y' }}</p>
<p>{{ order.financial_status_label }} · {{ order.fulfillment_status_label }}</p>
```

| Property                                                      | Type      | Description                                                     |
| ------------------------------------------------------------- | --------- | --------------------------------------------------------------- |
| `order.name`                                                  | string    | Display name, e.g. `#1001`                                      |
| `order.order_number`                                          | number    | Numeric order number                                            |
| `order.customer_url`                                          | string    | URL of the order's account page (`/account/orders/{id}`)        |
| `order.created_at`                                            | timestamp | Placement date                                                  |
| `order.cancelled`                                             | boolean   | Order was cancelled                                             |
| `order.cancelled_at`                                          | timestamp | Cancellation date                                               |
| `order.cancel_reason` / `order.cancel_reason_label`           | string    | Cancellation reason code / display label                        |
| `order.financial_status` / `order.financial_status_label`     | string    | Payment status code / display label                             |
| `order.fulfillment_status` / `order.fulfillment_status_label` | string    | Fulfillment status code / display label                         |
| `order.line_items`                                            | array     | [Line items](#order-line-item)                                  |
| `order.item_count`                                            | number    | Total quantity across line items                                |
| `order.subtotal_price`                                        | number    | Subtotal after line discounts                                   |
| `order.line_items_subtotal_price`                             | number    | Sum of line prices before order-level discounts                 |
| `order.total_price`                                           | number    | Order total                                                     |
| `order.total_net_amount`                                      | number    | Total minus refunds                                             |
| `order.total_refunded_amount`                                 | number    | Amount refunded                                                 |
| `order.tax_price`                                             | number    | Total tax                                                       |
| `order.tax_lines`                                             | array     | `{ title, rate, price }` per tax                                |
| `order.shipping_price`                                        | number    | Shipping total                                                  |
| `order.shipping_methods`                                      | array     | `{ title, price }` per shipping method                          |
| `order.shipping_address`                                      | object    | Shipping [address](#address)                                    |
| `order.billing_address`                                       | object    | Billing [address](#address)                                     |
| `order.discount_applications`                                 | array     | All discount applications                                       |
| `order.cart_level_discount_applications`                      | array     | Order-level discount applications                               |
| `order.total_discounts`                                       | number    | Total discount amount                                           |
| `order.transactions`                                          | array     | `{ kind, status, gateway, amount, created_at }` per transaction |
| `order.email` / `order.phone`                                 | string    | Contact used at checkout                                        |
| `order.note`                                                  | string    | Order note                                                      |
| `order.tags`                                                  | array     | Order tags                                                      |
| `order.metafields`                                            | object    | [Custom metafields](/aqua/metafields)                           |

<Note>
  All money values on `order`, its line items, and `customer.total_spent`
  are integers in the currency's **minor units** (e.g. cents). Always pipe
  through the `money` filter family for display.
</Note>

### order line item

| Property                                                       | Type    | Description                                                                             |
| -------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------- |
| `line_item.title`                                              | string  | Product + variant title                                                                 |
| `line_item.quantity`                                           | number  | Quantity ordered                                                                        |
| `line_item.sku`                                                | string  | SKU                                                                                     |
| `line_item.url`                                                | string  | Product URL                                                                             |
| `line_item.image`                                              | object  | Line item image                                                                         |
| `line_item.product` / `line_item.variant`                      | object  | Linked product / variant                                                                |
| `line_item.original_price` / `line_item.final_price`           | number  | Unit price before / after discounts                                                     |
| `line_item.original_line_price` / `line_item.final_line_price` | number  | Line total before / after discounts                                                     |
| `line_item.unit_price`                                         | number  | Unit-price measurement, where applicable                                                |
| `line_item.properties`                                         | object  | Custom line properties                                                                  |
| `line_item.fulfillment`                                        | object  | `{ tracking_company, tracking_number, tracking_url, created_at }`                       |
| `line_item.successfully_fulfilled_quantity`                    | number  | Quantity fulfilled so far                                                               |
| `line_item.requires_shipping`                                  | boolean | `false` for digital products                                                            |
| `line_item.selling_plan_allocation`                            | object  | Present when bought on a selling plan — see [platform extensions](#platform-extensions) |
| `line_item.digital_downloads`                                  | array   | Download links for digital products — see [platform extensions](#platform-extensions)   |

### address

Used by `customer.addresses`, `customer.default_address`,
`order.shipping_address`, and `order.billing_address`.

| Property                                   | Type   | Description                                              |
| ------------------------------------------ | ------ | -------------------------------------------------------- |
| `address.id`                               | string | Address ID                                               |
| `address.first_name` / `address.last_name` | string | Name parts                                               |
| `address.name`                             | string | Full name                                                |
| `address.company`                          | string | Company                                                  |
| `address.address1` / `address.address2`    | string | Street lines                                             |
| `address.street`                           | string | Combined street lines                                    |
| `address.city`                             | string | City                                                     |
| `address.province`                         | string | Province / state name                                    |
| `address.province_code`                    | string | Province / state code                                    |
| `address.country`                          | object | Country — renders as the name; has `name` and `iso_code` |
| `address.country_code`                     | string | ISO country code                                         |
| `address.zip`                              | string | Postal code                                              |
| `address.phone`                            | string | Phone                                                    |
| `address.url`                              | string | Edit URL (`/account/addresses/{id}`)                     |
| `address.summary`                          | string | One-line summary                                         |

Render a full address block with the `format_address` filter:

```aqua theme={null}
{{ order.shipping_address | format_address }}
```

## Platform extensions

The following properties are **LaunchMyStore-specific** — they go beyond
the classic customer-accounts object model, so guard them with `{% if %}`
in themes meant to stay portable.

### line\_item.selling\_plan\_allocation

Present on a line item when it was purchased on a
[selling plan](/aqua/selling-plans) (subscription):

| Property                                                    | Description                                 |
| ----------------------------------------------------------- | ------------------------------------------- |
| `selling_plan_allocation.selling_plan.id`                   | Selling plan ID                             |
| `selling_plan_allocation.selling_plan.name`                 | Display name, e.g. `Delivery every 1 Month` |
| `selling_plan_allocation.selling_plan.recurring_deliveries` | `true` for recurring plans                  |
| `selling_plan_allocation.selling_plan.options`              | Plan options (`{ name, value }`)            |

### order.subscription\_status

On orders that carry an active subscription, the current billing status:
`active`, `trialing`, `paused`, `past_due`, or `canceled`.

### line\_item.requires\_shipping and line\_item.digital\_downloads

Digital products have `requires_shipping: false` and, once the order is
paid, `digital_downloads` — an array of `{ name, url }` download links the
customer can use from their order page.

### Example: subscriptions + digital goods on customers/order

```aqua theme={null}
{% for line_item in order.line_items %}
  <div class="order-line">
    <a href="{{ line_item.url }}">{{ line_item.title }}</a>
    <span>× {{ line_item.quantity }}</span>
    <span>{{ line_item.final_line_price | money }}</span>

    {% comment %} Subscription line {% endcomment %}
    {% if line_item.selling_plan_allocation %}
      <p class="subscription-plan">
        {{ line_item.selling_plan_allocation.selling_plan.name }}
        {% if order.subscription_status %}
          <span class="badge badge--{{ order.subscription_status }}">
            {{ order.subscription_status }}
          </span>
        {% endif %}
      </p>
    {% endif %}

    {% comment %} Digital product: no shipping, show downloads {% endcomment %}
    {% unless line_item.requires_shipping %}
      {% if line_item.digital_downloads.size > 0 %}
        <ul class="downloads">
          {% for download in line_item.digital_downloads %}
            <li><a href="{{ download.url }}" download>{{ download.name }}</a></li>
          {% endfor %}
        </ul>
      {% endif %}
    {% endunless %}

    {% comment %} Physical product: tracking {% endcomment %}
    {% if line_item.fulfillment.tracking_number %}
      <p>
        {{ line_item.fulfillment.tracking_company }} —
        <a href="{{ line_item.fulfillment.tracking_url }}">
          {{ line_item.fulfillment.tracking_number }}
        </a>
      </p>
    {% endif %}
  </div>
{% endfor %}
```

## Helper JavaScript on account pages

Account pages automatically include a small platform helper script — themes
don't need to load anything.

**Country / province selector.** Populate the country `<select>` with the
`all_country_option_tags` object. Each emitted `<option>` carries the
country name as its value and a `data-provinces` attribute holding that
country's provinces as JSON. The helper wires a paired province `<select>`
to update whenever the country changes, and preselects the value named in
each select's `data-default` attribute:

```aqua theme={null}
<select id="AddressCountry" name="address[country]"
        data-default="{{ form.country }}">
  {{ all_country_option_tags }}
</select>

<select id="AddressProvince" name="address[province]"
        data-default="{{ form.province }}">
</select>
```

`country_option_tags` is also available and currently renders the same
full country list.

**POST-link helper.** The classic helper that submits a POST from a link —
used by address-book templates to delete an address by posting to
`/account/addresses/{id}` with `_method=delete`. Existing theme code that
calls the country/province and POST-link helpers works unmodified; the
plain-form alternative shown in the
[customer\_address section](#customer_address) works everywhere.

## Paginating orders

`customer.orders` works with the standard `{% paginate %}` tag:

```aqua theme={null}
{% paginate customer.orders by 20 %}
  <table>
    {% for order in customer.orders %}
      <tr>
        <td><a href="{{ order.customer_url }}">{{ order.name }}</a></td>
        <td>{{ order.created_at | date: '%b %d, %Y' }}</td>
        <td>{{ order.financial_status_label }}</td>
        <td>{{ order.fulfillment_status_label }}</td>
        <td>{{ order.total_net_amount | money }}</td>
      </tr>
    {% endfor %}
  </table>

  {% if paginate.pages > 1 %}
    {{ paginate | default_pagination }}
  {% endif %}
{% endpaginate %}
```

## SEO

Account pages are personal: the storefront emits
`<meta name="robots" content="noindex">` on every `customers/*` page and
excludes `/account` routes from the sitemap. Themes don't need to add
anything.
