---
title: "Display product approvals in the account area"
source: https://support.storeconnect.com/articles/display-product-approvals-in-the-account-area
type: article
format: markdown
site: StoreConnect Support — product and developer documentation for StoreConnect
site_index: https://storeconnect.com/llms.txt
docs_index: https://support.storeconnect.com/llms.txt
note: Append .md to any page or article URL on this site to get its Markdown form.
---
# Display product approvals in the account area

When you sell [restricted products](restricted-products), each approval is a record against a customer's Account. The account page can show those approvals back to the customer, so they know what they are cleared to buy, how much of their allowance is left, and when it runs out.

The base theme ships this section already. Use this process when you want to change what it shows, or add the approvals link to your account menu.

## Before you start

-   Set up at least one restricted product and grant an approval to a test Account. See [Restricted products](restricted-products).
-   Understand how the account page routes its sections. See [Theme layouts and pages](theme-layouts-and-pages).

## The section already exists

The account page reads `current_request.params.section` and renders one snippet per section. Product approvals is one of the sections the base theme registers:

| Section | URL | Snippet |
|---------|-----|---------|
| `product_approvals` | `/account?section=product_approvals` | `account/product_approvals` |

The router clause looks like this:


```liquid

{%- when "product_approvals" %}
  {% render "account/product_approvals", identifier: identifier %}
```


`snippets/account/product_approvals` is only a dispatcher: it renders `product_approvals/index` for the list and `product_approvals/show` for a single approval.

Override **`snippets/account/product_approvals/index`**, not the dispatcher. Replacing the dispatcher removes the detail page along with the list, and you do not need to touch the router either way.

## Where the data comes from

Approvals hang off the Account, not the Contact. The account page provides both `current_customer` (the signed-in Contact) and `current_account` (their Account), so read approvals from `current_account`.

| Liquid | Type | Notes |
|--------|------|-------|
| `current_account.product_approvals` | List[ProductApproval] | The approvals belonging to this Account |

Each entry is a [ProductApproval](product-approval-liquid-object-reference) drop, which renders the `s_c__Permitted_Restricted_Product__c` record. These are the attributes worth surfacing:

| Attribute | Type | Notes |
|-----------|------|-------|
| `approval_status` | String | One of `approved`, `completed`, `expired`, `pending`, or `none`. Prints as text, but reaches Liquid as a symbol, so cast it to a string before comparing it |
| `product` | Product | The approved product, when the approval is for a single product |
| `product_category` | ProductCategory | The approved category, when the approval covers a whole category |
| `approved_quantity` | Number | The maximum quantity this approval allows |
| `purchased_quantity` | Number | The quantity already bought on completed orders |
| `pending_quantity` | Number | The quantity already bought on submitted orders that have not completed |
| `remaining_quantity` | Number | The unused quantity still available |
| `unlimited?` | Boolean | True when the approval has no quantity limit |
| `approved_from` | Timestamp | When the approval starts, as a UTC timestamp |
| `approved_until` | Timestamp | When the approval ends, as a UTC timestamp |
| `order_items` | List[OrderItem] | The order items bought against this approval |

:::note
An approval is granted at product level or at category level, so one of `product` and `product_category` is blank on any given record. Check which one is populated rather than assuming `product` is always there, or the row renders with an empty name.
:::

## Build the section

This snippet lists the customer's approvals, handles both product and category approvals, and shows remaining quantity only where the approval is limited.


```liquid

<h1>Product approvals</h1>

{%- assign approvals = current_account.product_approvals %}

{%- if approvals.size > 0 %}
  <table>
    <thead>
      <tr>
        <th>Approved for</th>
        <th>Status</th>
        <th>Remaining</th>
        <th>Expires</th>
      </tr>
    </thead>
    <tbody>
      {%- for approval in approvals %}
        <tr>
          <td>
            {%- if approval.product != blank %}
              <a href="{{ approval.product.url }}">{{ approval.product.name }}</a>
            {%- elsif approval.product_category != blank %}
              {{ approval.product_category.name }}
            {%- else %}
              Approval
            {%- endif %}
          </td>
          <td>{{ approval.approval_status }}</td>
          <td>
            {%- if approval.unlimited? %}
              Unlimited
            {%- else %}
              {{ approval.remaining_quantity }} of {{ approval.approved_quantity }}
            {%- endif %}
          </td>
          <td>
            {%- if approval.approved_until != blank %}
              {{ approval.approved_until | date: "%b %-d, %Y", timezone: current_store.timezone }}
            {%- else %}
              No expiry
            {%- endif %}
          </td>
        </tr>
      {%- endfor %}
    </tbody>
  </table>
{%- else %}
  <p>You have no product approvals.</p>
{%- endif %}
```


**Pass a timezone when you format these dates.** `approved_from` and `approved_until` are UTC, so formatting them without `timezone:` can render a date a day out for customers whose local date differs from UTC, which matters most on the expiry date a customer is reading to decide whether to order today.

The markup above uses a plain table so it inherits your own styles. For how theme assets and stylesheets are loaded, see [Theme assets and styling](theme-assets-and-styling).

## Add the section to the account menu

Both `snippets/account/menu` and `snippets/header/dropdown/account` already show this link when the account has approvals, so there is usually nothing to add. Override one of them when you want to change the wording or where the entry sits, not to create it.

The existing entries are conditional for a reason, so keep the guard when you change one, or retail customers see a link to an empty page:


```liquid

{%- if current_account.product_approvals.size > 0 %}
  <a href="/account?section=product_approvals">Product approvals</a>
{%- endif %}
```


For the full account menu structure and how to add your own sections, see [Add a custom page to the account page menu](how-to-add-a-custom-page-to-the-account-page-menu).

## Show approval status on the product page

The same data is useful on the product page itself, where a customer is deciding whether to buy. Do not loop the account's approvals to find it: the product drop already carries the answer, and it accounts for approvals granted at category level, which a loop matching on product id would miss.

| Liquid | Returns |
|--------|---------|
| `product.approval_status` | This customer's status for this product: `approved`, `completed`, `expired`, `pending`, or `none`. Cast it to a string before comparing it. |
| `product.current_approved_quantity` | The maximum quantity this customer may buy. Returns `Infinity` when the approval is unlimited, and when the product is not restricted at all. |
| `product.pending_approval_date` | When a pending approval starts, or blank |

:::warning
Two things here fail silently rather than raising an error.

**`approval_status` reaches Liquid as a symbol, not a string.** Comparing it directly, as `product.approval_status == "approved"`, is always false, so the message never renders and nothing tells you why. Cast it first. Printing it with `{{ }}` is unaffected.

**`current_approved_quantity` returns `Infinity` for an unlimited approval.** Printing it unguarded shows the customer the word "Infinity" where a number should be.
:::


```liquid

{%- assign approval_state = product.approval_status | cast: "string" %}
{%- assign approved_quantity = product.current_approved_quantity | cast: "string" %}

{%- if approval_state == "approved" %}
  {%- if approved_quantity == "Infinity" %}
    <p class="SC-Notice">You are approved to buy this item.</p>
  {%- else %}
    <p class="SC-Notice">You can buy up to {{ approved_quantity }} of this item under your current approval.</p>
  {%- endif %}
{%- elsif approval_state == "pending" and product.pending_approval_date != blank %}
  <p class="SC-Notice">Your approval for this item starts {{ product.pending_approval_date | date: "%b %-d, %Y", timezone: current_store.timezone }}.</p>
{%- endif %}
```


Both attributes aggregate a product-level and a category-level approval, so this is correct where a loop over `product_approvals` would silently show nothing to a customer approved by category. See the [cast filter](cast-filter-reference).

:::warning
Do not treat what you render here as an access control. Whether a customer can actually buy a restricted product is enforced by StoreConnect when the order is placed, and the approval fields are for informing the customer, not for gating the purchase. Rendering an encouraging message does not grant permission, and hiding one does not remove it.
:::

Once the section is in place, an approved customer visiting `/account?section=product_approvals` sees each approval with its status, its remaining quantity, and its expiry date.

---

## Follow StoreConnect

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

## Machine-readable

- [Site index for agents](https://storeconnect.com/llms.txt): curated map of the StoreConnect site in llms.txt format
- [Documentation index for agents](https://support.storeconnect.com/llms.txt): full technical and product documentation map

Every page and article on this site has a Markdown rendering: append `.md` to its URL.

Continue in Markdown: [Help documentation](https://support.storeconnect.com/help-documentation.md) · [Developer reference](https://support.storeconnect.com/developer-reference.md) · [Videos & tutorials](https://support.storeconnect.com/videos-tutorials.md) · [Release notes](https://support.storeconnect.com/release-notes.md)

---

StoreConnect Support — https://support.storeconnect.com/articles/display-product-approvals-in-the-account-area