Skip to content
Log in

Add a custom page to the account page menu

On this page

The logged-in account area is built from a router template (pages/account) and one snippet per section. Adding a section means registering a new section value in the router, linking to it from the two navigation templates, and writing a snippet to render it.

Use this process when a customer needs a page that the standard account area does not provide. This example adds a Courses page that lists the courses a customer has taken, alongside the standard Orders and Subscriptions sections.

Custom Courses page added to the account navigation menu

Before you start

  • A theme record you can edit in your organization. See theme templates for how overrides are keyed.
  • A field on the Contact holding the data you want to show. This example reads a custom field, Course_History_Data__c, that stores a JSON array.
  • The current base-theme source for the three templates you will override. Copy each one as your starting point rather than pasting the excerpts below over a whole file, because the base markup changes between platform versions.

Templates used in this example

You override three existing templates:

  • pages/account
  • snippets/account/menu
  • snippets/header/dropdown/account

You add one new template:

  • snippets/courses/index

Step 1: Register the section in the account router

  1. Open your theme and create an override for the template key pages/account, starting from the current base-theme copy.
  2. Add a when clause for your new section inside the existing case block, then save.

```liquid

{%- when “courses” %} {% render “courses/index” %} ```

In context, alongside the sections the base theme already handles:

```liquid

{%- assign section = current_request.params.section %} {%- assign identifier = current_request.params.identifier %}

{%- case section %} {%- when “orders” %} {% render “account/orders”, identifier: identifier %} {%- when “courses” %} {% render “courses/index” %} {%- when “subscriptions” %} {% render “account/subscriptions”, identifier: identifier %} {%- else %} {% render “account/profile” %} {%- endcase %} ```

The router reads section from current_request.params and renders the matching snippet inside the account layout. Registering "courses" is what makes /account/courses resolve; without it the else branch renders the profile page instead.

Sections that show a single record pass identifier through to their snippet, as Orders does. This example lists every course on one page, so it does not need identifier.

The printable invoices and receipts article extends the same router for a different purpose, so read it before you add a second override to pages/account.

Restrict who can see the section

Registering a when clause makes the section reachable by any logged-in customer. The base theme gates its sensitive sections in the router rather than in the snippet, and renders account/not_authorised when the check fails:

```liquid

{%- when “account_credits” %} {%- if current_customer.can_use_account_credit? %} {% render “account/account_credits”, identifier: identifier %} {%- else %} {% render “account/not_authorised” %} {%- endif %} ```

Follow the same shape if your section shows data that not every customer should reach. Gate on a value you can verify on the Contact or Account, such as current_customer.membership or a custom field, and keep the check in the router so an unauthorized customer never reaches your snippet.

This example shows each customer only their own course data, read from their own Contact record, so it needs no additional check.

Step 2: Add the item to the account side menu

  1. Create an override for the template key snippets/account/menu, starting from the current base-theme copy.
  2. Add a list item for the new section after the Orders item, then save.

```liquid

<li class=”{% if current_request.params.section == “courses” %}is-current{% endif %}”> Courses </li> ```

Match the element and classes used by the items already in your copy of the template rather than the ones shown here. The base theme marks the active section with is-current, so comparing current_request.params.section against "courses" makes the new item highlight when the reader is on the Courses page.

Step 3: Add the item to the header account dropdown

  1. Create an override for the template key snippets/header/dropdown/account, starting from the current base-theme copy.
  2. Add a matching link after the Orders entry, then save.

```liquid

  • Courses
  • ```

    This is a different template from snippets/header/menu, which renders the main store navigation. The account dropdown only renders for a logged-in customer, so the link needs no additional guard.

    Step 4: Add the snippet that renders the page

    Create a new template with the key snippets/courses/index and add the following code.

    ```liquid

    {%- assign courses = current_customer.data[‘Course_History_Data__c’] deserialize %}

    Courses

    {%- if courses != blank and courses.size > 0 %}

    {%- for course in courses %} {%- endfor %}
    Name Result Letter grade Numeric grade Completed
    {{ course['name'] }} {{ course['result_status'] }} {{ course['letter_grade'] }} {{ course['numeric_grade'] }} {{ course['completed'] }}

    {%- else %}

    No course history.

    {%- endif %} ```

    The deserialize filter parses the JSON string held in the custom field into a list you can iterate. A parse failure returns nil rather than raising, and an input over 1 MB also returns nil, which is why the guard checks != blank before reading size. Test the guard by pointing the field at malformed JSON: the page should render “No course history.” rather than a Liquid error.

    Custom fields are reached through .data['Field__c'] on the Contact drop. Only fields the theme is permitted to read are available, so confirm the field is exposed before relying on it.

    Replace the table with whatever markup the page needs. This snippet is the only place in the example that owns its own markup, so it is the safest place to extend.

    Reading from records instead of a JSON field

    A JSON string on the Contact suits data that arrives from an external system as one blob. It has limits: deserialize returns nil above 1 MB, and you cannot filter or paginate the contents in the query itself.

    When the data already lives in its own related records, query those records instead of packing them into a field. See the query tag for the syntax and Liquid queries for filtering, ordering, and pagination.

    Translate the menu label (optional)

    If your store serves more than one language, replace the literal Courses text in steps 2 and 3 with a translation key and add the matching entry to your locale files. See theme locales.

    ```liquid

    {{ “accounts.menu.courses” | t }} ```

    A missing key renders the key itself, so add the locale entry in the same change as the template edit.

    Log in to the storefront and open /account/courses. The Courses item appears in the account side menu and in the header account dropdown, is marked as the current section, and the page lists the customer’s course history.

    Was this article helpful?

    Was this article helpful?