# Theme layouts and pages

Source: https://support.storeconnect.com/articles/theme-layouts-and-pages · Last modified 21 August 2026

A **layout** is the outermost template that wraps page content in a complete HTML document. It typically contains the `<html>`, `<head>`, and `<body>` elements, along with common elements like the site header, footer, navigation, and asset includes.

**Page templates render before the layout.** The platform renders the page template into a string, then passes that string as the `body_content` variable to the layout. The layout wraps the pre-rendered page content — it does not "call" the page.

## The default layout

The default layout is `layouts/theme.liquid`. All pages use this layout unless a page template specifies a different one. Here is a minimal layout:


```liquid

{%- default body_content: nil, csrf_meta_tags: nil, csp_meta_tag: nil -%}
{%- default title: nil, meta_keywords: nil, meta_description: nil -%}
{%- default sc_support: nil, theme_bar: nil, data: nil -%}
{%- default controller: nil, action: nil, id: nil -%}
{%- default theme_supplement_stylesheet: nil, theme_supplement_javascript: nil -%}
<!doctype html>
<html lang="{{ current_store.locale }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {{ csrf_meta_tags }}
    {{ csp_meta_tag }}
    {% render "meta_data" %}
    {%- require "styles/theme.css" %}
    {{ theme_supplement_stylesheet }}
    {%- require "scripts/theme.js" %}
    {{ theme_supplement_javascript }}
    {{ sc_support }}
  </head>
  <body id="{{ id }}" {{ data }}>
    {{ theme_bar }}
    <a href="#SC-Main" class="skip-link">{{ "accessibility.skip_to_content" | t | default: "Skip to main content" }}</a>
    {% render "header" %}
    <main id="SC-Main">
      {% render "flash" %}
      {{ body_content }}
    </main>
    {% render "footer" %}
  </body>
</html>
```


## Required layout variables

The platform passes these variables to `layouts/theme.liquid`. Declare all of them with `{% default %}` at the top of your layout to prevent undefined variable errors. Variables marked **required** will cause visible failures if omitted.

| Variable | Required | Description |
|----------|----------|-------------|
| `body_content` | **YES** | The fully rendered page template output. Without this, the page appears blank. |
| `csrf_meta_tags` | **YES** | HTML `<meta>` tags containing CSRF tokens. Without this, all form submissions fail. |
| `csp_meta_tag` | **YES** | Content Security Policy `<meta>` tag. |
| `sc_support` | **YES** | Platform support scripts. |
| `theme_bar` | **YES** | Theme editor and preview bar HTML. Without this, the theme editor breaks. |
| `theme_supplement_stylesheet` | **YES** | `<link>` tag for custom theme CSS (`theme-supplement.css`). Without this, custom theme styles do not load. |
| `theme_supplement_javascript` | **YES** | `<script>` tag for custom theme JS (`theme-supplement.js`). Without this, custom theme scripts do not load. |
| `title` | no | Page title, auto-generated per page type (product name, article title, store name). Used by the `meta_data` snippet. |
| `meta_keywords` | no | SEO keywords for the page. |
| `meta_description` | no | SEO description for the page. |
| `controller` | no | Name of the controller handling the request (for example `products`, `carts`). Useful for body classes or conditional logic. |
| `action` | no | Controller action name (for example `show`, `index`). |
| `id` | no | Page identifier string, formatted as `SC-{controller}-{action}` (for example `SC-products-show`). Intended for the `<body>` element's `id` attribute. |
| `data` | no | Extra data attributes for the `<body>` element. |
| `format` | no | Response format if non-HTML (for example `txt`, `json`). |

:::warning
Always output `{{ body_content }}`, `{{ csrf_meta_tags }}`, and `{{ theme_bar }}` in your layout. Omitting any of these causes visible failures: blank pages, broken forms, or a broken theme editor.
:::

## Rendering order

1. The Liquid controller's `{% before %}` phase runs.
2. The page template renders into a string.
3. The rendered string is passed as `body_content` to `layouts/theme.liquid`.
4. The layout renders, outputting `{{ body_content }}` where the page content should appear.
5. The Liquid controller's `{% after %}` and `{% final %}` phases run.

## Multiple layouts

You can define additional layouts for distinct sections of your site. For example, an account layout that adds a sidebar:


```liquid

{%- comment -%} layouts/account.liquid {%- endcomment -%}
<article class="sc-container">
  <div class="SC-Grid">
    <div class="SC-Grid_sidebar">
      {% render "account/menu" %}
    </div>
    <div class="SC-Grid_main">
      {{ yield }}
    </div>
  </div>
</article>
```


Secondary layouts render within the main `theme.liquid`. The `{{ yield }}` variable is where the page content appears inside the secondary layout, while the secondary layout's output becomes `body_content` in the main layout.

To use a non-default layout, add a `{% layout %}` tag at the top of the page template:


```liquid

{% layout "account" %}
```


If no `{% layout %}` tag is present, `theme.liquid` is used.

## Page routing table

Page templates render for specific URL patterns. The file name determines the route mapping.

### Core pages

| Page template | URL pattern | Context variable |
|---------------|-------------|-----------------|
| `home.liquid` | `/` | `current_page` |
| `product.liquid` | `/products/:slug` | `current_product` |
| `products.liquid` | `/products`, `/products/category/:path` | `current_search`, `current_product_category` |
| `product_category.liquid` | `/products/category/:path` | `current_product_category`, `current_search` |
| `cart.liquid` | `/cart` | `current_cart` |
| `checkout.liquid` | `/checkout`, `/checkout/:step` | `current_cart`, `current_checkout_step` |
| `order.liquid` | `/orders/:token` | `current_order` |
| `page.liquid` | `/pages/:path` | `current_page` |
| `article.liquid` | `/articles/:path` | `current_article` |
| `article_category.liquid` | `/articles/category/:path` | `current_article_category` |
| `search.liquid` | `/search` | `current_search` |
| `account.liquid` | `/account`, `/account/:section` | `current_customer`, `current_account` |
| `location.liquid` | `/locations/:slug` | `current_location` |
| `locations.liquid` | `/locations` | (locations collection) |
| `not_found.liquid` | any unmatched URL | `error` |
| `maintenance.liquid` | when store is in maintenance mode | — |
| `form_submission.liquid` | `/form-submissions/:id` | `current_form_submission` |
| `voucher.liquid` | `/vouchers/:code` | `current_voucher` |
| `subscription.liquid` | `/subscriptions/:id` | `current_subscription` |

### Authentication pages

Authentication pages live in the `pages/auth/` subdirectory:

| Page template | URL pattern | Purpose |
|---------------|-------------|---------|
| `auth/login.liquid` | `/login` | Login form |
| `auth/register.liquid` | `/register` | Registration form |
| `auth/password/forgot.liquid` | `/password/forgot` | Forgot password form |
| `auth/password/reset.liquid` | `/password/reset` | Reset password form |
| `auth/confirmation/pending.liquid` | `/confirmation/pending` | Email confirmation pending |
| `auth/confirmation/resend.liquid` | `/confirmation/resend` | Resend confirmation email |
| `auth/invitation/accept.liquid` | `/invitation/accept` | Accept invitation |
| `auth/invitation/pending.liquid` | `/invitation/pending` | Invitation pending |
| `auth/missing_details.liquid` | `/missing-details` | Complete profile after login |

## Global variables

These variables are available on every page template:

| Variable | Type | Description |
|----------|------|-------------|
| `current_store` | StoreDrop | Current store configuration |
| `current_cart` | CartDrop | Shopping cart |
| `current_customer` | ContactDrop | Logged-in customer (nil if not logged in) |
| `current_account` | AccountDrop | Logged-in customer's account |
| `current_request` | RequestDrop | HTTP request info (path, params, etc.) |
| `current_flash` | FlashDrop | Flash messages (notice, alert, error) |
| `all_products` | Collection | All products |
| `all_pages` | Collection | All content pages |
| `all_articles` | Collection | All articles |
| `all_product_categories` | Collection | All product categories |
| `all_article_categories` | Collection | All article categories |
| `all_menus` | Collection | Navigation menus |
| `all_content_blocks` | Collection | Content blocks |
| `all_media` | Collection | Media files |
| `session_variables` | Hash | Session variables (set with `{% session %}`) |
| `theme_variables` | Hash | Theme variables |

## Checkout steps

The checkout page uses `current_checkout_step` to determine which step to display:

| Step | Value | Description |
|------|-------|-------------|
| Customer information | `"customer_information"` | Collect customer details |
| Shipping information | `"shipping_information"` | Select shipping method |
| Accept terms | `"accept_terms"` | Terms and conditions |
| Payment information | `"payment_information"` | Payment method and submission |


```liquid

{% case current_checkout_step %}
{% when "customer_information" %}
  {% form "checkout-customer-information" %}
    {% render "checkout/customer_information/form", form: form %}
  {% endform %}
{% when "shipping_information" %}
  {% form "checkout-shipping-information" %}
    {% render "checkout/shipping_information/form", form: form %}
  {% endform %}
{% when "accept_terms" %}
  {% form "checkout-accept-terms" %}
    {% render "checkout/accept_terms/form", form: form %}
  {% endform %}
{% when "payment_information" %}
  {% component "checkout/payment_information/page", reload: "sc.voucher-applied sc.voucher-removed" %}
{% endcase %}
```


:::note
The payment information step must use `{% component %}`, not a `{% form %}` block. The component handles the payment provider JavaScript that powers the payment fields.
:::

## Account sections

The account page uses `current_request.params.section` to route to different account areas:

| Section | URL | Description |
|---------|-----|-------------|
| (default) | `/account` | Profile overview |
| `orders` | `/account?section=orders` | Order history |
| `carts` | `/account?section=carts` | Saved carts |
| `fulfillments` | `/account?section=fulfillments` | Fulfillment tracking |
| `subscriptions` | `/account?section=subscriptions` | Subscriptions |
| `account_credits` | `/account?section=account_credits` | Account credits |
| `account_points` | `/account?section=account_points` | Loyalty points |
| `product_approvals` | `/account?section=product_approvals` | Product approval requests |
| `credentials` | `/account?section=credentials` | Change password |
| `contact` | `/account?section=contact` | Edit contact information |
| `shipping` | `/account?section=shipping` | Shipping addresses |
| `billing` | `/account?section=billing` | Billing addresses |

## Content-type variants

Some pages support multiple output formats. The variant file renders when the corresponding format is requested:

| File | Content type | Example URL |
|------|-------------|-------------|
| `page.liquid` | HTML | `/pages/about` |
| `page.json.liquid` | JSON | `/pages/about.json` |
| `page.xml.liquid` | XML | `/pages/about.xml` |
| `page.csv.liquid` | CSV | `/pages/about.csv` |
| `page.md.liquid` | Markdown | `/pages/about.md` |
| `page.text.liquid` | Plain text | `/pages/about.txt` |

Articles support all the same variants.

## Home page

The home page template typically renders the content blocks configured for the store's home page:


```liquid

{% if current_page != blank %}
  {{ current_page.body_content }}
{% else %}
  <p>Configure a home page in your store settings.</p>
{% endif %}
```


The home page is linked to a content page in the store configuration. That content page's blocks (slideshows, featured products, text sections, and so on) are rendered via `body_content`.

:::tip
`current_page.body_content` (used in `pages/home.liquid`) and `body_content` (used in `layouts/theme.liquid`) are different variables. The first renders the CMS content blocks on the page object. The second is the fully rendered page template output passed to the layout.
:::

---

## Follow StoreConnect

- [Email Newsletter](https://getstoreconnect.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://getstoreconnect.com/partners)
- [News](https://getstoreconnect.com/articles/news)
- [Events](https://getstoreconnect.com/articles/events)
- [Feature Comparison](https://getstoreconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://getstoreconnect.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

---

StoreConnect Support — https://support.storeconnect.com/articles/theme-layouts-and-pages