{"title":"Display product approvals in the account area","slug":"display-product-approvals-in-the-account-area","url":"https://support.storeconnect.com/articles/display-product-approvals-in-the-account-area","url_markdown":"https://support.storeconnect.com/articles/display-product-approvals-in-the-account-area.md","subtitle":null,"summary":"Customize the product approvals section of the account page so business customers can see what restricted products they are approved to buy, how much quantity remains, and when each approval expires. Use this when you sell restricted products to approved accounts.","type":"Developer_Documentation","video_url":"","keywords":"product approvals, account area, restricted products, permitted restricted product, approval status, approved quantity, remaining quantity, b2b account, account section, product_approvals, account menu, liquid, why can my customer not buy this, customer cannot see price, show customers what they are approved to buy, approval expired, how much can they order, licensed products, age restricted, trade only, wholesale approval","last_modified":"2026-09-15T02:32:04+0000","body_markdown":"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.\n\nThe 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.\n\n## Before you start\n\n-   Set up at least one restricted product and grant an approval to a test Account. See [Restricted products](restricted-products).\n-   Understand how the account page routes its sections. See [Theme layouts and pages](theme-layouts-and-pages).\n\n## The section already exists\n\nThe account page reads `current_request.params.section` and renders one snippet per section. Product approvals is one of the sections the base theme registers:\n\n| Section | URL | Snippet |\n|---------|-----|---------|\n| `product_approvals` | `/account?section=product_approvals` | `account/product_approvals` |\n\nThe router clause looks like this:\n\n\n```liquid\n\n{%- when \"product_approvals\" %}\n  {% render \"account/product_approvals\", identifier: identifier %}\n```\n\n\n`snippets/account/product_approvals` is only a dispatcher: it renders `product_approvals/index` for the list and `product_approvals/show` for a single approval.\n\nOverride **`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.\n\n## Where the data comes from\n\nApprovals 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`.\n\n| Liquid | Type | Notes |\n|--------|------|-------|\n| `current_account.product_approvals` | List[ProductApproval] | The approvals belonging to this Account |\n\nEach 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:\n\n| Attribute | Type | Notes |\n|-----------|------|-------|\n| `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 |\n| `product` | Product | The approved product, when the approval is for a single product |\n| `product_category` | ProductCategory | The approved category, when the approval covers a whole category |\n| `approved_quantity` | Number | The maximum quantity this approval allows |\n| `purchased_quantity` | Number | The quantity already bought on completed orders |\n| `pending_quantity` | Number | The quantity already bought on submitted orders that have not completed |\n| `remaining_quantity` | Number | The unused quantity still available |\n| `unlimited?` | Boolean | True when the approval has no quantity limit |\n| `approved_from` | Timestamp | When the approval starts, as a UTC timestamp |\n| `approved_until` | Timestamp | When the approval ends, as a UTC timestamp |\n| `order_items` | List[OrderItem] | The order items bought against this approval |\n\n:::note\nAn 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.\n:::\n\n## Build the section\n\nThis snippet lists the customer's approvals, handles both product and category approvals, and shows remaining quantity only where the approval is limited.\n\n\n```liquid\n\n\u003ch1\u003eProduct approvals\u003c/h1\u003e\n\n{%- assign approvals = current_account.product_approvals %}\n\n{%- if approvals.size \u003e 0 %}\n  \u003ctable\u003e\n    \u003cthead\u003e\n      \u003ctr\u003e\n        \u003cth\u003eApproved for\u003c/th\u003e\n        \u003cth\u003eStatus\u003c/th\u003e\n        \u003cth\u003eRemaining\u003c/th\u003e\n        \u003cth\u003eExpires\u003c/th\u003e\n      \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n      {%- for approval in approvals %}\n        \u003ctr\u003e\n          \u003ctd\u003e\n            {%- if approval.product != blank %}\n              \u003ca href=\"{{ approval.product.url }}\"\u003e{{ approval.product.name }}\u003c/a\u003e\n            {%- elsif approval.product_category != blank %}\n              {{ approval.product_category.name }}\n            {%- else %}\n              Approval\n            {%- endif %}\n          \u003c/td\u003e\n          \u003ctd\u003e{{ approval.approval_status }}\u003c/td\u003e\n          \u003ctd\u003e\n            {%- if approval.unlimited? %}\n              Unlimited\n            {%- else %}\n              {{ approval.remaining_quantity }} of {{ approval.approved_quantity }}\n            {%- endif %}\n          \u003c/td\u003e\n          \u003ctd\u003e\n            {%- if approval.approved_until != blank %}\n              {{ approval.approved_until | date: \"%b %-d, %Y\", timezone: current_store.timezone }}\n            {%- else %}\n              No expiry\n            {%- endif %}\n          \u003c/td\u003e\n        \u003c/tr\u003e\n      {%- endfor %}\n    \u003c/tbody\u003e\n  \u003c/table\u003e\n{%- else %}\n  \u003cp\u003eYou have no product approvals.\u003c/p\u003e\n{%- endif %}\n```\n\n\n**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.\n\nThe 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).\n\n## Add the section to the account menu\n\nBoth `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.\n\nThe 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:\n\n\n```liquid\n\n{%- if current_account.product_approvals.size \u003e 0 %}\n  \u003ca href=\"/account?section=product_approvals\"\u003eProduct approvals\u003c/a\u003e\n{%- endif %}\n```\n\n\nFor 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).\n\n## Show approval status on the product page\n\nThe 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.\n\n| Liquid | Returns |\n|--------|---------|\n| `product.approval_status` | This customer's status for this product: `approved`, `completed`, `expired`, `pending`, or `none`. Cast it to a string before comparing it. |\n| `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. |\n| `product.pending_approval_date` | When a pending approval starts, or blank |\n\n:::warning\nTwo things here fail silently rather than raising an error.\n\n**`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.\n\n**`current_approved_quantity` returns `Infinity` for an unlimited approval.** Printing it unguarded shows the customer the word \"Infinity\" where a number should be.\n:::\n\n\n```liquid\n\n{%- assign approval_state = product.approval_status | cast: \"string\" %}\n{%- assign approved_quantity = product.current_approved_quantity | cast: \"string\" %}\n\n{%- if approval_state == \"approved\" %}\n  {%- if approved_quantity == \"Infinity\" %}\n    \u003cp class=\"SC-Notice\"\u003eYou are approved to buy this item.\u003c/p\u003e\n  {%- else %}\n    \u003cp class=\"SC-Notice\"\u003eYou can buy up to {{ approved_quantity }} of this item under your current approval.\u003c/p\u003e\n  {%- endif %}\n{%- elsif approval_state == \"pending\" and product.pending_approval_date != blank %}\n  \u003cp class=\"SC-Notice\"\u003eYour approval for this item starts {{ product.pending_approval_date | date: \"%b %-d, %Y\", timezone: current_store.timezone }}.\u003c/p\u003e\n{%- endif %}\n```\n\n\nBoth 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).\n\n:::warning\nDo 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.\n:::\n\nOnce 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."}