dashboard.liquid tags are grouped by default vs optional usage.
- Default components are already present in the shipped
dashboard.liquidtemplate. - Optional components can be added or used to replace parts of the default layout.
{% legacy_navbar %}.
Delivery offset for custom timelines
project.delivery_days_after_shipment_date exposes the project’s configured delivery offset as an integer number of days. It defaults to 0 when no offset is configured, so custom timelines can use the same setting as the delivery-date picker instead of hardcoding it.
Default components
{% promotional_offer %}
Shows an offer when the current subscription is eligible for one. If more than one offer applies, Firmhouse shows the
pending offer with the lowest priority number. The card includes the offer title, description, benefit, commitment when
present, and a link where the authenticated customer can review and accept or decline the offer.
The default dashboard.liquid template includes this tag. It renders only when Workflow Builder is enabled in
New Features and the subscription has an eligible offer that has not been accepted or declined.
subscription.eligible_promotional_offers instead. The collection is sorted
by priority and exposes every pending eligible offer:
offer.reactivates_subscription, a boolean that is true when accepting the offer reactivates
a cancelled subscription or resumes a paused one. Use it to hide a template’s standard reactivation or resume prompt
when the displayed offer already performs that action, so campaigns do not show both at once:
Pause and resume actions
The defaultdashboard.liquid template includes pause or resume action links when subscription pausing is enabled for the project and the current subscription state allows the action.
Use the same conditions in custom templates:
{% subscription_pause_resume_banner %}
Block-style pause or resume callout.
The default dashboard.liquid template includes this tag near the top of the dashboard for paused subscriptions, and below the shipment/frequency controls for active subscriptions. It renders when subscription pausing is enabled for the project and the current subscription can be paused or resumed. Active subscriptions show pause copy and a pause button. Paused subscriptions show paused-state copy and a resume button.
{% subscription_reactivation_banner %}
Lets customers restart a cancelled subscription from Customer Portal v2. It appears only for cancelled subscriptions when Customer Portal Subscription Reactivation is enabled in New Features.
The current default dashboard.liquid template already includes this component. If your project uses a customized dashboard template, add the tag where customers should see the reactivation form:
ReferralCandy banner
The defaultdashboard.liquid template includes a ReferralCandy banner when subscription.referral_candy.url is available for the current subscription.
The default banner:
- Links More info to the customer’s ReferralCandy invite page (
subscription.referral_candy.url) - Copies the customer’s ReferralCandy invite link from
subscription.referral_candy.url - Shows the configured advocate reward discount from
subscription.referral_candy.advocate_reward_discountwhen Firmhouse can resolve a valid reward for the subscription - Falls back to a generic translated message when the reward discount is unavailable
subscription.referral_candy.url is only available when Firmhouse can fetch an invite link from ReferralCandy for the subscription. If the ReferralCandy campaign is stopped, paused, or configured in a way that prevents invite links from being created, the value is blank and the default banner stays hidden.
{% prepaid_renewal %}
Prepaid renewal notice and call to action.
The default dashboard.liquid template includes this tag. It renders a renewal block for prepaid subscriptions when the renewal date is coming up and the prepaid subscription was created from a regular subscription through a prepaid upgrade.
Customers can open the renewal options page from this block. On that page, they can keep renewing prepaid, switch back to the original regular billing subscription, or update the renewal payment date without changing their renewal option.
{% simple_next_billing_date_with_confirm %}
Inline editable next billing date control.
{% simple_plan_switch_with_confirm %}
Inline plan switch select with confirmation modal.
label_format: "both" combines plan name and frequency:
{% manage_products %}
Primary CTA to manage existing products.
{% product_quantity_progress %}
Progress and status widget for plan-based product quantity rules.
This component only renders when the active plan has a minimum and/or maximum product quantity configured.The default
dashboard.liquid already includes this tag, and you can move or add it elsewhere in the template.
Sample scenarios:
{% product_listing %}
Main list of active ordered products.
When your project treats out-of-stock Shopify products as unavailable for new orders, a product on the list that has since gone out of stock automatically shows an Out of stock badge next to its title. Custom templates that render ordered products directly instead of through this tag can use the ordered_product.out_of_stock Liquid attribute to render an equivalent badge.
To replace crossed-out prices with a savings line, pass
price_display: "savings". The default “You save %” message is translated automatically into the portal’s language, with translations for all ten supported languages:
[data-role="product-savings"] in your template. Omitting price_display keeps the existing price display.
Optionally override the message with savings_text. Use %{amount} for the formatted savings amount:
product_listing_filtered, including shipment_schedule: "recurring" when the wording should apply only to recurring products.
{% product_listing_filtered %}
Filtered list of active ordered products. Shows the same Out of stock badge as product_listing above when applicable.
Use this tag when you want to split the dashboard product list into separate groups. If you do not pass a filter parameter, it renders the same active ordered products as the regular listing.
shipment_schedule groups products by shipment schedule:
"recurring"shows products that ship more than once."one_time_purchase"shows products where the effective interval isonly_once.
shipment_schedule is passed.
shipment_schedule with a metadata filter:
show_replacement_button: true when customers should be able to replace products from that list. Replacement flows only swap the selected product and keep the existing ordered product quantity. Use hide_replacement_price: true when the replacement catalog and confirmation page should not show replacement prices.
Both flows can also be opened directly from an email with a hotlink, using the ordered product Liquid attributes in transactional email templates:
{{product.replace_url | ssc_hotlink}}opens the same replacement catalog as the replacement button, where the customer picks any available product as a replacement.{{product.swap_variant_url | ssc_hotlink}}opens the same options screen as the swap button, where the customer switches between variants of the product (scoped to the same product group when Product swapping is configured that way).
ssc_hotlink filter logs the customer in automatically, so one click in the email lands them in the flow. See Product availability & stock rules for a worked example that links out-of-stock products to these flows from the upcoming order notice.
{% product_alternatives %}
Offer eligible alternatives when a customer already has a product in a configured group. The tag assigns an array of available switches; it renders no markup. Groups with no current subscription product, or an unavailable alternative, are skipped.
Configure groups using Firmhouse product IDs in your dashboard template:
[[123, 456, 789], [101, 102]]) or a hash for two-product groups ({"123": 456}), for example from project.metadata.product_groups. Set assign_to: "switches" to use another result variable.
Each result contains ordered_product (the existing ordered-product Liquid object), replacement_product (the existing product Liquid object), confirmation_url, and replacement_url. The confirmation link opens the existing replacement confirmation page with the alternative selected.
To switch immediately from the dashboard, submit a POST form to replacement_url instead:
replacement_url.
Both options preserve the current quantity and use the existing replacement behavior. This tag only offers switches between products configured with interval_unit_of_measure: on_billing_cycle. Current products and alternatives with one-time or independent day/week/month schedules are excluded, as are explicit one-off add-on lines and subscriptions on prepaid plans. It does not convert one-off purchases to recurring products. Labels, loading indicators, and suggestions after switching remain template customizations.
This uses product replacement, rather than the variant-swap picker. It requires Customer Portal v2, a dynamic-subscription project, product changes enabled, and New Add Products Flow in Self Service Center enabled in New Features. See New Add Products Flow. Only active, customer-manageable lines are included. Alternatives must satisfy the existing project, country, plan-group, interval, collection-availability and stock rules. The confirmation and submission endpoints recheck eligibility. Group configuration controls the links shown; it does not grant access to otherwise unavailable products or restrict other replacement entry points.
{% recommended_products %}
Assigns eligible products from a Firmhouse collection without rendering markup.
Use collection_id to select a collection belonging to the subscription’s project,
limit to show up to 8 products (default 4), and optionally assign_to to change
the result variable from recommended_products.
Products follow collection position order and retain the portal’s availability,
stock, country and plan eligibility rules. Recommendations are empty when product
changes are disabled, the project is not dynamic, or the subscription is stopped,
prepaid, or a one-time purchase.
Each entry contains a standard product Liquid object, in_subscription,
options and add_url. Each option exposes one_time
and a formatted, tax-aware price, calculated by the existing addition flow.
Recurring products can offer both purchase modes using the same product ID.
One-time products offer only a one-time option. Separate catalog products are not
automatically paired. Products already active on the subscription have no add options.
prices contains the same one-unit purchase-mode prices even when the product is
already in the subscription. Use it for price presentation and keep options
for add buttons. ordered_product identifies the active line, and remove_url
is present when that line is customer-manageable. Submit a POST form to that URL
with _method=delete to remove it. Merchant-specific
required-product rules must also be applied to these removal controls.
plan_discount_percentage is populated only for an eligible recurring product
with a percentage plan discount, unless Shopify recurring pricing takes precedence.
Copy belongs in the template, for example
in Repeat (−{{ recommendation.plan_discount_percentage }}%), or a translated
label using the percentage as an interpolation value. This describes the plan
discount, not the difference between purchase modes; actual one-time and recurring
prices can be equal under the subscription’s pricing rules.
{% next_order_price_overview %}
Core upcoming-order summary card.
When
show_discount_code_entry is enabled, the component shows a discount code field below the order summary. Customers can enter an active Firmhouse discount code and apply it to their subscription. After a valid code is applied, the upcoming order totals refresh automatically.
Automatic refresh
Setauto_refresh: true when order prices can still change a few seconds after the order summary renders, for example while shipping costs are recalculated in the background. The component then reloads itself once after auto_refresh_delay seconds. The refresh keeps the items and show_discount_code_entry settings of the original tag.
The delayed reload runs after the dashboard loads and again after each change to the upcoming order — a product quantity change, a removed product, or an applied discount code. A loading spinner covers the card from each trigger until the refreshed totals arrive. Each change schedules a single reload; quick consecutive changes are grouped into one.
Machine-readable amounts
When an upcoming order is available, the component exposes its price breakdown as integer cents on the component’s root element. All amounts include tax. Use these attributes for custom template behavior instead of parsing localized currency text.
The attributes are available regardless of which rows you select with
items, except for the shipping attribute while shipping is unavailable. The rendered total row also keeps data-order-price-overview-total-amount-cents with the final amount for backwards compatibility.
Enable discount code entry
Turn on Discount Codes in Customer Portal v2 in New Features for the project. The default Customer Portal v2 dashboard currently renders this component withshow_discount_code_entry: false. Remove that parameter or set it to true in your custom dashboard.liquid template if customers should be able to apply discount codes from the portal.
Firmhouse only shows the discount code field when the subscription does not already have an active promotion that blocks manual discount codes.
Accepted discount codes must meet these conditions:
- The discount code is active and has not reached its usage limit.
- The linked promotion is active and available for the customer’s country.
- The subscription has no active manual discount, including a promotion that was applied by another discount code.
- Any active automatic discount on the subscription allows combining with manual discounts. In the discount settings, enable Allow this automatic discount to be combined with manual discounts.
- The code is not a referral discount code. Referral codes are intended for new customer signup and first-order discounts, not recurring orders for existing customers.
- For Shopify subscription contracts, the promotion should have simple rules to be synced to Shopify as a subscription contract discount. Codes for more complex Firmhouse-only advanced promotion rules are not supported yet.
{% minimum_order_amount_warning %}
Warning shown when the next order preview does not meet the project’s minimum order amount.
The default dashboard.liquid template includes this tag before the product list. It renders a warning only when all of these conditions are true:
- The project is a dynamic subscription project.
- The subscription is active.
- Firmhouse can calculate the next order preview.
- The next order subtotal before shipping and discounts is below the configured minimum order amount for the subscription country.
{% add_product %}
Secondary CTA to browse catalog and add products.
Optional components
{% asset_purchase_offers %}
Shows every asset on the subscription that the customer is currently eligible to purchase. The component renders nothing when no purchase offers are available.
This component has no parameters.
{% asset_purchase_offer %}
Renders one asset purchase offer from an asset ownership Liquid object. Prefer asset_purchase_offers when Firmhouse should find and render every eligible offer automatically.
{% non_paying_banner %}
Displays an account warning when the subscription is marked as non-paying. It renders nothing for subscriptions in good payment standing.
{% latest_orders %}
Recent orders list.
{% next_order_preview %}
Upcoming order preview card.
Advanced product_listing_filtered patterns
Filtered product list card (for segmented subscriptions).
show_replacement_button is enabled, customers can replace products in that filtered list. Replacement keeps the current ordered product quantity. Set hide_replacement_price: true if replacement prices should stay hidden in the catalog and confirmation step.
Dynamic groups based on ordered product metadata
If yourfilter_value should adapt to subscriber-specific metadata (for example names like "Veronika" or "Michiel"), you can build the group list from subscription.ordered_products and then render product_listing_filtered in a loop.
How are dynamic values fetched?
You can do this directly indashboard.liquid; no custom backend endpoint is required.
subscription.ordered_productsis available in SSCv2 Liquid context.- Each
ordered_productincludesmetadata. - Reading
ordered_product.metadata.groupgives the current subscription’s group value for that ordered product (or useordered_product.metadata.personif your project still uses the legacy key). - Pass
filter_bywith the same metadata key (grouporperson) that produced the value. - Looping over ordered products lets you discover all group values present on the active subscription at render time.
- hardcoded sections stay explicit (
Add-ons,Gifts,One-time extras) - dynamic sections include only metadata values currently present on the subscription
- hardcoded values are excluded from the dynamic loop
- exclusions and de-duplication are done case-insensitively (
downcase)
ordered_product.metadata.group (and optional ordered_product.metadata.person fallback) with your key and pass that same key to filter_by.
Shopify instalment progress from ordered products
If your Shopify subscription setup uses instalment billing, you can render progress directly from the ordered products indashboard.liquid.
ordered_product.instalment_current_numberreturns the current instalment number for that subscription line.ordered_product.instalment_total_countreturns the total instalment count from the subscription’s active plan.subscription.active_plan.instalmentsis also available when you want to render plan-level instalment details outside the ordered product loop.