{"title":"Theme layouts and pages","slug":"theme-layouts-and-pages","url":"https://support.storeconnect.com/articles/theme-layouts-and-pages","url_markdown":"https://support.storeconnect.com/articles/theme-layouts-and-pages.md","subtitle":null,"summary":"Layouts wrap page content in a complete HTML document, with the required layout variables, the full page routing table, and how authentication, checkout, and account pages are structured.","type":"Developer_Documentation","video_url":"","keywords":"theme layout, layouts, pages, routing, page templates, body_content, csrf_meta_tags, theme_bar, checkout steps, account sections, layout variables, page routing","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"A **layout** is the outermost template that wraps page content in a complete HTML document. It typically contains the `\u003chtml\u003e`, `\u003chead\u003e`, and `\u003cbody\u003e` elements, along with common elements like the site header, footer, navigation, and asset includes.\n\n**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.\n\n## The default layout\n\nThe default layout is `layouts/theme.liquid`. All pages use this layout unless a page template specifies a different one. Here is a minimal layout:\n\n\n```liquid\n\n{%- default body_content: nil, csrf_meta_tags: nil, csp_meta_tag: nil -%}\n{%- default title: nil, meta_keywords: nil, meta_description: nil -%}\n{%- default sc_support: nil, theme_bar: nil, data: nil -%}\n{%- default controller: nil, action: nil, id: nil -%}\n{%- default theme_supplement_stylesheet: nil, theme_supplement_javascript: nil -%}\n\u003c!doctype html\u003e\n\u003chtml lang=\"{{ current_store.locale }}\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    {{ csrf_meta_tags }}\n    {{ csp_meta_tag }}\n    {% render \"meta_data\" %}\n    {%- require \"styles/theme.css\" %}\n    {{ theme_supplement_stylesheet }}\n    {%- require \"scripts/theme.js\" %}\n    {{ theme_supplement_javascript }}\n    {{ sc_support }}\n  \u003c/head\u003e\n  \u003cbody id=\"{{ id }}\" {{ data }}\u003e\n    {{ theme_bar }}\n    \u003ca href=\"#SC-Main\" class=\"skip-link\"\u003e{{ \"accessibility.skip_to_content\" | t | default: \"Skip to main content\" }}\u003c/a\u003e\n    {% render \"header\" %}\n    \u003cmain id=\"SC-Main\"\u003e\n      {% render \"flash\" %}\n      {{ body_content }}\n    \u003c/main\u003e\n    {% render \"footer\" %}\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\n## Required layout variables\n\nThe 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.\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `body_content` | **YES** | The fully rendered page template output. Without this, the page appears blank. |\n| `csrf_meta_tags` | **YES** | HTML `\u003cmeta\u003e` tags containing CSRF tokens. Without this, all form submissions fail. |\n| `csp_meta_tag` | **YES** | Content Security Policy `\u003cmeta\u003e` tag. |\n| `sc_support` | **YES** | Platform support scripts. |\n| `theme_bar` | **YES** | Theme editor and preview bar HTML. Without this, the theme editor breaks. |\n| `theme_supplement_stylesheet` | **YES** | `\u003clink\u003e` tag for custom theme CSS (`theme-supplement.css`). Without this, custom theme styles do not load. |\n| `theme_supplement_javascript` | **YES** | `\u003cscript\u003e` tag for custom theme JS (`theme-supplement.js`). Without this, custom theme scripts do not load. |\n| `title` | no | Page title, auto-generated per page type (product name, article title, store name). Used by the `meta_data` snippet. |\n| `meta_keywords` | no | SEO keywords for the page. |\n| `meta_description` | no | SEO description for the page. |\n| `controller` | no | Name of the controller handling the request (for example `products`, `carts`). Useful for body classes or conditional logic. |\n| `action` | no | Controller action name (for example `show`, `index`). |\n| `id` | no | Page identifier string, formatted as `SC-{controller}-{action}` (for example `SC-products-show`). Intended for the `\u003cbody\u003e` element's `id` attribute. |\n| `data` | no | Extra data attributes for the `\u003cbody\u003e` element. |\n| `format` | no | Response format if non-HTML (for example `txt`, `json`). |\n\n:::warning\nAlways 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.\n:::\n\n## Rendering order\n\n1. The Liquid controller's `{% before %}` phase runs.\n2. The page template renders into a string.\n3. The rendered string is passed as `body_content` to `layouts/theme.liquid`.\n4. The layout renders, outputting `{{ body_content }}` where the page content should appear.\n5. The Liquid controller's `{% after %}` and `{% final %}` phases run.\n\n## Multiple layouts\n\nYou can define additional layouts for distinct sections of your site. For example, an account layout that adds a sidebar:\n\n\n```liquid\n\n{%- comment -%} layouts/account.liquid {%- endcomment -%}\n\u003carticle class=\"sc-container\"\u003e\n  \u003cdiv class=\"SC-Grid\"\u003e\n    \u003cdiv class=\"SC-Grid_sidebar\"\u003e\n      {% render \"account/menu\" %}\n    \u003c/div\u003e\n    \u003cdiv class=\"SC-Grid_main\"\u003e\n      {{ yield }}\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/article\u003e\n```\n\n\nSecondary 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.\n\nTo use a non-default layout, add a `{% layout %}` tag at the top of the page template:\n\n\n```liquid\n\n{% layout \"account\" %}\n```\n\n\nIf no `{% layout %}` tag is present, `theme.liquid` is used.\n\n## Page routing table\n\nPage templates render for specific URL patterns. The file name determines the route mapping.\n\n### Core pages\n\n| Page template | URL pattern | Context variable |\n|---------------|-------------|-----------------|\n| `home.liquid` | `/` | `current_page` |\n| `product.liquid` | `/products/:slug` | `current_product` |\n| `products.liquid` | `/products`, `/products/category/:path` | `current_search`, `current_product_category` |\n| `product_category.liquid` | `/products/category/:path` | `current_product_category`, `current_search` |\n| `cart.liquid` | `/cart` | `current_cart` |\n| `checkout.liquid` | `/checkout`, `/checkout/:step` | `current_cart`, `current_checkout_step` |\n| `order.liquid` | `/orders/:token` | `current_order` |\n| `page.liquid` | `/pages/:path` | `current_page` |\n| `article.liquid` | `/articles/:path` | `current_article` |\n| `article_category.liquid` | `/articles/category/:path` | `current_article_category` |\n| `search.liquid` | `/search` | `current_search` |\n| `account.liquid` | `/account`, `/account/:section` | `current_customer`, `current_account` |\n| `location.liquid` | `/locations/:slug` | `current_location` |\n| `locations.liquid` | `/locations` | (locations collection) |\n| `not_found.liquid` | any unmatched URL | `error` |\n| `maintenance.liquid` | when store is in maintenance mode | — |\n| `form_submission.liquid` | `/form-submissions/:id` | `current_form_submission` |\n| `voucher.liquid` | `/vouchers/:code` | `current_voucher` |\n| `subscription.liquid` | `/subscriptions/:id` | `current_subscription` |\n\n### Authentication pages\n\nAuthentication pages live in the `pages/auth/` subdirectory:\n\n| Page template | URL pattern | Purpose |\n|---------------|-------------|---------|\n| `auth/login.liquid` | `/login` | Login form |\n| `auth/register.liquid` | `/register` | Registration form |\n| `auth/password/forgot.liquid` | `/password/forgot` | Forgot password form |\n| `auth/password/reset.liquid` | `/password/reset` | Reset password form |\n| `auth/confirmation/pending.liquid` | `/confirmation/pending` | Email confirmation pending |\n| `auth/confirmation/resend.liquid` | `/confirmation/resend` | Resend confirmation email |\n| `auth/invitation/accept.liquid` | `/invitation/accept` | Accept invitation |\n| `auth/invitation/pending.liquid` | `/invitation/pending` | Invitation pending |\n| `auth/missing_details.liquid` | `/missing-details` | Complete profile after login |\n\n## Global variables\n\nThese variables are available on every page template:\n\n| Variable | Type | Description |\n|----------|------|-------------|\n| `current_store` | StoreDrop | Current store configuration |\n| `current_cart` | CartDrop | Shopping cart |\n| `current_customer` | ContactDrop | Logged-in customer (nil if not logged in) |\n| `current_account` | AccountDrop | Logged-in customer's account |\n| `current_request` | RequestDrop | HTTP request info (path, params, etc.) |\n| `current_flash` | FlashDrop | Flash messages (notice, alert, error) |\n| `all_products` | Collection | All products |\n| `all_pages` | Collection | All content pages |\n| `all_articles` | Collection | All articles |\n| `all_product_categories` | Collection | All product categories |\n| `all_article_categories` | Collection | All article categories |\n| `all_menus` | Collection | Navigation menus |\n| `all_content_blocks` | Collection | Content blocks |\n| `all_media` | Collection | Media files |\n| `session_variables` | Hash | Session variables (set with `{% session %}`) |\n| `theme_variables` | Hash | Theme variables |\n\n## Checkout steps\n\nThe checkout page uses `current_checkout_step` to determine which step to display:\n\n| Step | Value | Description |\n|------|-------|-------------|\n| Customer information | `\"customer_information\"` | Collect customer details |\n| Shipping information | `\"shipping_information\"` | Select shipping method |\n| Accept terms | `\"accept_terms\"` | Terms and conditions |\n| Payment information | `\"payment_information\"` | Payment method and submission |\n\n\n```liquid\n\n{% case current_checkout_step %}\n{% when \"customer_information\" %}\n  {% form \"checkout-customer-information\" %}\n    {% render \"checkout/customer_information/form\", form: form %}\n  {% endform %}\n{% when \"shipping_information\" %}\n  {% form \"checkout-shipping-information\" %}\n    {% render \"checkout/shipping_information/form\", form: form %}\n  {% endform %}\n{% when \"accept_terms\" %}\n  {% form \"checkout-accept-terms\" %}\n    {% render \"checkout/accept_terms/form\", form: form %}\n  {% endform %}\n{% when \"payment_information\" %}\n  {% component \"checkout/payment_information/page\", reload: \"sc.voucher-applied sc.voucher-removed\" %}\n{% endcase %}\n```\n\n\n:::note\nThe payment information step must use `{% component %}`, not a `{% form %}` block. The component handles the payment provider JavaScript that powers the payment fields.\n:::\n\n## Account sections\n\nThe account page uses `current_request.params.section` to route to different account areas:\n\n| Section | URL | Description |\n|---------|-----|-------------|\n| (default) | `/account` | Profile overview |\n| `orders` | `/account?section=orders` | Order history |\n| `carts` | `/account?section=carts` | Saved carts |\n| `fulfillments` | `/account?section=fulfillments` | Fulfillment tracking |\n| `subscriptions` | `/account?section=subscriptions` | Subscriptions |\n| `account_credits` | `/account?section=account_credits` | Account credits |\n| `account_points` | `/account?section=account_points` | Loyalty points |\n| `product_approvals` | `/account?section=product_approvals` | Product approval requests |\n| `credentials` | `/account?section=credentials` | Change password |\n| `contact` | `/account?section=contact` | Edit contact information |\n| `shipping` | `/account?section=shipping` | Shipping addresses |\n| `billing` | `/account?section=billing` | Billing addresses |\n\n## Content-type variants\n\nSome pages support multiple output formats. The variant file renders when the corresponding format is requested:\n\n| File | Content type | Example URL |\n|------|-------------|-------------|\n| `page.liquid` | HTML | `/pages/about` |\n| `page.json.liquid` | JSON | `/pages/about.json` |\n| `page.xml.liquid` | XML | `/pages/about.xml` |\n| `page.csv.liquid` | CSV | `/pages/about.csv` |\n| `page.md.liquid` | Markdown | `/pages/about.md` |\n| `page.text.liquid` | Plain text | `/pages/about.txt` |\n\nArticles support all the same variants.\n\n## Home page\n\nThe home page template typically renders the content blocks configured for the store's home page:\n\n\n```liquid\n\n{% if current_page != blank %}\n  {{ current_page.body_content }}\n{% else %}\n  \u003cp\u003eConfigure a home page in your store settings.\u003c/p\u003e\n{% endif %}\n```\n\n\nThe 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`.\n\n:::tip\n`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.\n:::"}