> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ordergroove.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ordergroove.com/_mcp/server.

# Subscription Manager Development Guide

Ordergroove's Subscription Manager is built with a custom implementation of Nunjucks to maximize readability and ease of development without compromising performance on your store. This guide is aimed at developers with a good understanding of HTML, CSS, and JavaScript who are looking to customize their Subscription Manager.

***

## Requirements

This guide is relevant for developers working on either v0 or v25 of the Subscription Manager. All examples are taken from v25.

***

## Overview

Ordergroove's Subscription Manager is built on a custom implementation of Nunjucks which transpiles into a [lit-html web component](https://lit.dev/docs/libraries/standalone-templates/) to be sent to the customer. This implementation enables the Subscription Manager to be built with the intuitive feel of a templating language, while maximizing DOM responsiveness to changes by the customer without necessitating a full-page refresh.

During development pre-compilation, the Subscription Manager templates are written as a combination of Nunjucks and lit-html.

***

## Fundamentals

This section walks through the fundamentals of Ordergroove's Subscription Manager templating language.

### Nunjucks Tags and Expressions

The primary means of logic in the Subscription Manager are Nunjucks tags and expressions. Key tags that are implemented are `if`, `set`, `for`, and `include`. These tags are used in `{% %}` brackets, just like in Nunjucks, and can be used to add logic around variables and what you want to render.

Example of `if` and `set`:

```liquid
{% if not subscription %}
  {% set badge_class = "og-badge-one-time" %}
{% elseif subscription.prepaid_subscription_context %}
  {% set badge_class = "og-badge-prepaid" %}
{% else %}
  {% set badge_class = "og-badge-pay-as-you-go" %}
{% endif %}
```

Example of `for` and `include`:

```liquid
{% for order in orders %}
  {% include 'order/main' %}
{% endfor %}
```

The `set` keyword sets variables to primitives or objects. The `include` keyword injects another `.liquid` file into that location — when a file is included, its code is inserted as if it were placed there directly. You always need to include the absolute folder path of the partial from the base `views` folder in your `include` path, even if you're referencing a file from the same subfolder.

Nunjucks variables and expressions can be displayed using `{{ }}` brackets. For example, `<p>{{ badge_class }}</p>` would display the value of `badge_class` in a paragraph.

### Objects

The Subscription Manager relies on objects containing customer subscription data. On load, the Subscription Manager retrieves all data for the customer and stores it in the Redux store. Objects from the Redux store can be accessed directly through the Nunjucks template.

Some objects are documented in the [Object Reference](/docs/lifecycle/sm/object-reference), but the Redux store itself is an excellent place to check what is available for your customers, since some features may be enabled on your store that aren't shown in the documentation. See the [Debugging with Redux](/docs/lifecycle/sm/redux-debugging) guide for more information.

### Filters and Pipes

Nunjucks filters are used with pipes to modify and manipulate objects, variables, and strings. To use a filter, place a pipe and the filter inside Nunjucks brackets. For example, the `t` filter indicates that the string preceding it should be passed to the locales files to be matched with text content in the current language. `{{ 'cancel_subscription_button' | t }}` inserts the translation for the `cancel_subscription_button` key in the locales file.

Filters can be chained left to right, with the output of the left-most filter serving as the input for the next filter.

The `| js` filter takes in a JavaScript function. It can be passed either the name of a JavaScript function in `script.js`, or an inline function as a string — for example, `'(e) => console.log(e)' | js`. The next section discusses lit-html event bindings, the primary usage for this filter.

The `| action` filter wires up a form to a JavaScript handler that validates the data and submits it to Ordergroove's APIs. These handler functions are not available to external developers.

The other implemented filters are described in the table below. More details can be found at [https://static.ordergroove.com/@ordergroove/smi-core/latest/docs/index.html](https://static.ordergroove.com/@ordergroove/smi-core/latest/docs/index.html) in the *Filters* folder.

| Filter       | Usage                                                                                                                                                                                                                 |
| :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currency`   | Returns a localized currency number. Internally using [NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)                                              |
| `date`       | Returns a localized date in the specified format. Formats follow dayjs — full format docs [here](https://day.js.org/docs/en/display/format). See [Working with Dates](/docs/lifecycle/sm/dates) for more information. |
| `dump`       | Stringifies the passed value                                                                                                                                                                                          |
| `entries`    | Returns the result of JavaScript's `Object.entries`                                                                                                                                                                   |
| `find`       | Implements [lodash find](https://lodash.com/docs/4.17.15#find)                                                                                                                                                        |
| `first`      | Implements [lodash first](https://lodash.com/docs/4.17.15#first)                                                                                                                                                      |
| `get`        | Implements [lodash get](https://lodash.com/docs/4.17.15#get)                                                                                                                                                          |
| `if_defined` | Implements `ifDefined` from [lit-html](https://lit-html.polymer-project.org/guide/template-reference#ifdefined)                                                                                                       |
| `key`        | Extends the given list with a key, to allow efficient updates when the list changes                                                                                                                                   |
| `last`       | Implements [lodash last](https://lodash.com/docs/4.17.15#last)                                                                                                                                                        |
| `reject`     | Implements [lodash reject](https://lodash.com/docs/4.17.15#reject). Limits an array to items that don't meet the passed criteria                                                                                      |
| `select`     | Creates an identity function based on an optional filter function. If no filter function is provided and the argument is a string of length 32, a default filter using `public_id` will be used                       |
| `shuffle`    | Creates an array of shuffled values using a version of the [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle)                                                                                 |
| `sort`       | Implements [lodash sort](https://lodash.com/docs/4.17.15#sort)                                                                                                                                                        |
| `until`      | Implements [lit-html until](https://lit.dev/docs/v1/lit-html/template-reference/#until)                                                                                                                               |

If you need a filter that is not already implemented, please create a ZenDesk ticket with the filter and desired functionality.

### Lit-HTML Expressions

Because Subscription Manager files ultimately compile to lit-html, many of lit-html's expressions are available in addition to Nunjucks-style templating.

**Event Listeners**

The Ordergroove Subscription Manager supports adding event listeners to HTML elements with `@` expressions through lit-element. Lit's documentation on this is available [here](https://lit.dev/docs/components/events/#adding-event-listeners-in-the-element-template).

For example, `@click="{{ 'SMDialog.open' | js }}"` means that on the `click` event, the JavaScript method `open` from the `SMDialog` custom element will be called.

**Boolean Checks**

Boolean attributes in lit-element allow conditional setting of HTML attributes using `?` at the beginning of the attribute. Lit's documentation on this is available [here](https://lit.dev/docs/templates/expressions/#boolean-attribute-expressions).

**Property Setting**

Lit-html's property syntax allows setting a property on an element instead of the attribute. Lit's documentation on this is available [here](https://lit.dev/docs/templates/expressions/#property-expressions).

For example, `<input .value=${value}>` sets the `value` property directly rather than the attribute (which would only set the default value). This is useful when setting a complex data property like an array or object, or when setting an element's property differs from setting its attribute.

### Comments

Comments in the Subscription Manager are marked within `{# #}` brackets. Anything within these brackets will not be displayed in the DOM.

***

## Common Mistakes

**Property Accesses on Null Objects**

Variables in the Subscription Manager have global scope but are not always defined when the Subscription Manager loads. This can lead to inconsistent loading states if a variable is used without checking whether it is defined first. The most common example is accessing a property on an object — like `product.name` — without first establishing that `product` is defined and non-null. If you have made customizations to your SM and encounter loading issues, check all property accesses and ensure the object is non-null.

**Duplicate IDs**

Some Subscription Manager liquid files render inputs with specific IDs. For example, `sku-swap-product-change-dialog.liquid` includes `<label>` elements with a `for` attribute set to the `id` of the corresponding `<input>`:

```html
<input
  required
  type="radio"
  name="product"
  value="{{alt_product_id}}"
  class="visually-hidden"
  id="alternative-product-{{alt_product_id}}-for-{{product.external_product_id}}-in-{{order_item.public_id}}"
>
<label
  data-product-name="{{ alt_product.name }}"
  for="alternative-product-{{alt_product_id}}-for-{{product.external_product_id}}-in-{{order_item.public_id}}"
>
```

In HTML, IDs are global. If you include this file multiple times on your theme when making customizations, you may end up with multiple `<input>` elements sharing the same ID. This can cause issues with those inputs — if you're having trouble selecting a radio input option, check that it's the only input on the page with that ID.