# Next Commerce: Storefront > Storefront themes and Theme Kit, template objects, tags and filters, theme settings, the storefront GraphQL API, checkout links, and event tracking. This is one of 6 domain bundles derived from the platform capability map at https://developers.nextcommerce.com/capabilities.json (generated 2026-09-03). The other bundles are listed at https://developers.nextcommerce.com/llms.txt. Merchant and operator guides are on https://docs.nextcommerce.com; the changelog is https://docs.nextcommerce.com/changelog. The Admin API contract is the OpenAPI file at https://developers.nextcommerce.com/api/admin/2024-04-01.yaml. ## Capabilities ### Storefront themes (id: storefront-themes) The hosted storefront is rendered from a theme built with Django Template Language. Merchants install and customise themes under Storefront > Themes; developers build themes locally with Theme Kit (the ntk CLI: ntk pull, ntk push, ntk watch), starting from the Spark starter theme, and query storefront data through the GraphQL API. Status: available. Audiences: merchant, developer. Links verified: 2026-09-03. Merchant and operator guides (docs.nextcommerce.com): - https://docs.nextcommerce.com/docs/build-a-store/storefront - https://docs.nextcommerce.com/docs/build-a-store/storefront/themes - https://docs.nextcommerce.com/docs/build-a-store/storefront/pages-and-assets Developer guides (developers.nextcommerce.com): - https://developers.nextcommerce.com/docs/storefront - https://developers.nextcommerce.com/docs/storefront/themes - https://developers.nextcommerce.com/docs/storefront/themes/theme-kit - https://developers.nextcommerce.com/docs/storefront/themes/settings - https://developers.nextcommerce.com/docs/storefront/themes/translations - https://developers.nextcommerce.com/docs/storefront/themes/cdn-and-caching - https://developers.nextcommerce.com/docs/storefront/themes/templates - https://developers.nextcommerce.com/docs/storefront/themes/templates/objects - https://developers.nextcommerce.com/docs/storefront/themes/templates/tags - https://developers.nextcommerce.com/docs/storefront/themes/templates/filters - https://developers.nextcommerce.com/docs/storefront/themes/templates/urls-and-template-paths - https://developers.nextcommerce.com/docs/storefront/themes/guides/custom-page-templates - https://developers.nextcommerce.com/docs/storefront/themes/guides/custom-product-templates - https://developers.nextcommerce.com/docs/storefront/themes/guides/product-variants - https://developers.nextcommerce.com/docs/storefront/themes/guides/product-metadata - https://developers.nextcommerce.com/docs/storefront/themes/guides/personalized-products - https://developers.nextcommerce.com/docs/storefront/graphql - https://developers.nextcommerce.com/docs/storefront/event-tracking Admin API operations, version 2024-04-01 (13): - [GET /pages/ — Retrieve a list of pages.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesList) - [POST /pages/ — Create a new page.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesCreate) - [GET /pages/{id}/ — Retrieve a page.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesRetrieve) - [PUT /pages/{id}/ — Update an existing page.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesUpdate) - [PATCH /pages/{id}/ — Partially update an existing page.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesPartialUpdate) - [DELETE /pages/{id}/ — Delete an existing page.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/pagesDestroy) - [GET /themes/ — Retrieve a list of themes.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesList) - [POST /themes/ — Create a new theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesCreate) - [GET /themes/{id}/ — Retrieve a theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesRetrieve) - [DELETE /themes/{id}/ — Delete an existing theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesDestroy) - [GET /themes/{id}/templates/ — Retrieve a list of templates in an existing theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesTemplatesRetrieve) - [POST /themes/{id}/templates/ — Create a new template in an existing theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesTemplatesCreate) - [DELETE /themes/{id}/templates/ — Delete an existing template in an existing theme.](https://developers.nextcommerce.com/docs/admin-api/reference/storefront/themesTemplatesDestroy) AI agent skills: - [next-theme-dev](https://github.com/NextCommerceCo/skills/tree/main/next-theme-dev) - [next-theme-figma](https://github.com/NextCommerceCo/skills/tree/main/next-theme-figma) ### Checkout links (id: checkout-links) Prebuilt URLs that open the storefront checkout with products, quantities, and attribution already applied, for use from ads, emails, and campaign pages. Status: available. Audiences: merchant, developer. Links verified: 2026-09-03. Merchant and operator guides (docs.nextcommerce.com): - https://docs.nextcommerce.com/docs/features/offers/shareable-coupon-links Developer guides (developers.nextcommerce.com): - https://developers.nextcommerce.com/docs/storefront/checkout-links ## Pages The full text of every developer page cited above, in the order listed. # Storefront (https://developers.nextcommerce.com/docs/storefront) The Next Commerce storefront is a flexible, customizable front-end layer for your ecommerce business. Whether you're building a completely custom storefront or enhancing an existing theme, this section will guide you through the tools and features available to developers. ### Themes [#themes] Themes allow you to fully control the look and feel of your storefront using modern front-end technologies. Each theme includes layouts, templates, stylesheets, and scripts, giving you full creative freedom to build a branded customer experience. * Customize homepage, products, catalog, and pages * Easily manage theme assets (CSS, JS, images) * Customize themes via the dashboard or CLI (Theme Kit) Learn how to [build and manage your theme →](https://developers.nextcommerce.com/docs/storefront/themes) ### Event Tracking [#event-tracking] Capture user behavior, conversion events, and key storefront interactions with built-in event tracking tools. * Track add-to-cart, purchases, and other standard ecommerce events * Hook into page views, checkout events, and custom triggers * Integrate storefront events with external platforms Learn how to [implement tracking for your storefront →](https://developers.nextcommerce.com/docs/storefront/event-tracking) ### Storefront GraphQL API [#storefront-graphql-api] Power deeper customizations, dynamic content loading, and headless experiences using our Storefront GraphQL API. * Fetch products, cart data, and more in real-time. * Create custom functionality such as cart upsells. Learn how to [query your storefront data →](https://developers.nextcommerce.com/docs/storefront/graphql) # Themes (https://developers.nextcommerce.com/docs/storefront/themes) > **Tip:** We highly recommend using Theme Kit to manage your store theme for the best developer experience from your favorite IDE. [Read the Theme Kit guide](https://developers.nextcommerce.com/docs/storefront/themes/theme-kit) — installation, configuration, and every `ntk` command. ### Layout & Structure [#layout--structure] The Storefront theme framework has a set guideline for the base directories of your theme for your assets, html, and settings. ```bash title="Storefront Theme Structure" theme ├── assets ├── checkout ├── configs ├── layouts ├── locales ├── partials ├── sass └── templates ``` ### Assets [#assets] The assets directory is used to upload static asset files used in the theme such as images, stylesheets, web fonts, and javascript files. The assets directory works in conjunction with the [`asset_url` template filter](https://developers.nextcommerce.com/docs/storefront/themes/templates/filters#asset_url) to render the full path on your storefront. ```jinja title="Example link in template to /assets/css/style.css" {{ 'css/style.css'|asset_url }} ``` ### Configs [#configs] The configs directory is used to store your theme settings options and also the settings data as they should be configured with the theme. * `settings_schema.json` is used to generate the theme settings form * `setting_data.json` is used data storage for the the theme settings ```bash title="Config Directory" configs ├── settings_data.json └── settings_schema.json ``` [See Theme Settings Guide](https://developers.nextcommerce.com/docs/storefront/themes/settings) ### Locales [#locales] The `locales` directory is used to for storefront theme translation json files. The translation files are used in conjunction with the translation template tag to support multiple languages for your storefront. Translation files should be named according to the ISO 639-1 2 letter language code standard. The default or fallback language should be denoted with a .default in the file name as shown below. See the [Translations guide](https://developers.nextcommerce.com/docs/storefront/themes/translations) for more examples on how to localize your theme content. ```bash title="Locale Files Example" locales ├── en.default.json ├── de.json ├── es.json ├── it.json └── fr.json ``` [See Translations Guide](https://developers.nextcommerce.com/docs/storefront/themes/translations) ### Layouts [#layouts] The `layouts` directory is used to store base templates that are then extended from in view specific templates, see extends and block template tags for more on template inheritance. See the [`extends` template tag](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#extends--block) for more on template inheritance. ```bash title="Layouts Directory Example" layouts └── base.html ``` ### Partials [#partials] The partials directory is used to store reusable which are reusable snippets of code that can be used in tandem with the include template tag for reuse across many templates. See the includes template tag for more on template inheritance with partials. ```bash title="Partials Directory Example" partials ├── header.html ├── footer.html └── pagination.html ``` ```jinja title="Example Partial Include Another Template" {% include "partials/footer.html" %} ``` ### Templates [#templates] The templates directory is used to store all templates for a theme, see [URLs and Template Paths](https://developers.nextcommerce.com/docs/storefront/themes/templates/urls-and-template-paths) for reference. ```bash title="Templates Directory Example" templates ├── 403.html ├── 404.html ├── 500.html ├── blog │   ├── index.html │   └── post.html ├── cart.html ├── catalogue │   ├── category.html │   ├── index.html │   └── product.html ├── index.html ├── pages │   └── page.html ├── reviews │   ├── form.html │   ├── index.html │   └── review.html ├── search.html └── support ├── article.html ├── category.html └── index.html ``` ### Sass [#sass] The sass directory accepts scss files for use in in a theme. See Theme Kit for more details on local sass compiling. > **Warning:** Sass files are not automatically compiled in the platform and must be compiled to css files locally for use in templates from the assets directory. ### Theme Kit [#theme-kit] [Theme Kit](https://github.com/NextCommerceCo/theme-kit) is a command line tool for developers to build an maintain storefront themes programmatically, allowing theme developers to: * Work on theme templates and assets using their local code editor or favorite IDE. * Use git version control to work on a theme collectively with many theme collaborators. * Use a pipeline to manage deployments of theme updates. > **Tip:** See the [Theme Kit guide](https://developers.nextcommerce.com/docs/storefront/themes/theme-kit) for installation, configuration, and commands. Source: [GitHub](https://github.com/NextCommerceCo/theme-kit). # Theme Kit (https://developers.nextcommerce.com/docs/storefront/themes/theme-kit) [Theme Kit](https://github.com/NextCommerceCo/theme-kit) is a command line tool for developers to build and maintain storefront themes programmatically, allowing theme developers to: * Work on theme templates and assets using their local code editor or favorite IDE. * Use git version control to work on a theme collectively with many theme collaborators. * Use a pipeline to manage deployments of theme updates. [See Full Instructions on Github](https://github.com/NextCommerceCo/theme-kit) or [Install Theme Kit from PyPi](https://pypi.org/project/next-theme-kit/) ### Installation [#installation] Theme Kit is a python package available on [PyPi](https://pypi.org/project/next-theme-kit/) If you already have `python` and `pip`, install with the following command: ```bash title="Installation" pip install next-theme-kit ``` #### Mac OSX Requirements [#mac-osx-requirements] See how to install `python` and `pip` with [HomeBrew](https://docs.brew.sh/Homebrew-and-Python#python-3x). Once you have completed this step you can install using the `pip` instructions above. #### Windows Requirements [#windows-requirements] * **Option 1 (Recommended)** - Windows 10 and above feature WSL (Windows Subsystem for Linux) which provides a native Linux environment, see how to [Install WSL with Ubuntu](https://docs.microsoft.com/en-us/windows/wsl/install). Once you have installed WSL, follow the [best practice guides to configure and use with VS Code](https://docs.microsoft.com/en-us/windows/wsl/setup/environment) and then follow the `pip` instructions above to install Theme Kit. * **Option 2** - Installing `python` in Windows natively can be done with through the [Windows App Store](https://apps.microsoft.com/store/detail/python-39/9P7QFQMJRFP7?hl=en-us\&gl=us). Recommend using [Windows Powershell](https://apps.microsoft.com/store/detail/powershell/9MZ1SNWT0N5D?hl=en-us\&gl=us). This route is a little more tricky and some knowledge on how to manage python in windows will be required. > **Tip:** **Use Python Virtual Environments** - For Mac, Windows, and Linux, it's a best practice to use a Python Virtual Environment to isolate python packages and dependencies to reduce potential conflicts or errors, [more on creating a Python Virtual Environment](https://www.freecodecamp.org/news/how-to-setup-virtual-environments-in-python/). ### Setup [#setup] Connect `ntk` to a store in three steps. #### 1. Create the API Key [#1-create-the-api-key] Store authentication uses [OAuth 2.0](https://auth0.com/intro-to-iam/what-is-oauth-2/) and requires creating a store OAuth App with the `themes:read` and `themes:write` permissions. 1. In the Storefront admin, go to **Settings > API Access**. 2. Click **Create App**. 3. Give the app a name and assign a user. 4. In the **Permissions** tab, enable `themes:read` and `themes:write`. 5. **Save**. Copy the generated API key — you will need it in the next step. #### 2. Configure Theme Kit [#2-configure-theme-kit] `ntk` reads its connection settings from two places: command flags (`--apikey`, `--store`, `--theme_id`) and the `config.yml` file in your theme directory. You do not need to create `config.yml` by hand — `ntk checkout` and `ntk init` write it for you, and after that commands run without flags: ```yaml title="config.yml (written by ntk checkout / ntk init)" development: apikey: store: https://{store}.29next.store theme_id: ``` > **Warning:** Keep the API key out of source control. Do not commit `config.yml` to git if it contains the key. > **Note:** `config.yml` supports multiple environments. Commands use the `development` entry by default; pass `-e` / `--env` to target another environment (e.g. `ntk push --env=production`). The `[development]` prefix in command output is the active environment. #### 3. Connect to a Theme [#3-connect-to-a-theme] Work from a copy of an existing theme rather than an empty directory — a complete theme is the reference for the required directories, templates, and settings. **Work on a theme already on the store** — `ntk checkout` downloads the theme into your current directory and writes `config.yml`: ```bash ntk checkout --theme_id= --apikey="" --store="https://{store}.29next.store" ``` **Add a new theme to the store** — start from a copy of an existing theme, such as the [Spark](https://github.com/NextCommerceCo/spark) starter theme, then register it as a new theme with `ntk init` and upload the files with `ntk push`: ```bash ntk init --name="" --apikey="" --store="https://{store}.29next.store" ntk push ``` ### Usage [#usage] With the package installed, you can now use the commands inside your theme directory and work on a storefront theme. | Command | Description | | -------------- | --------------------------------------------------------------- | | `ntk init` | Initialize a new theme | | `ntk list` | List all available themes | | `ntk checkout` | Checkout an existing theme | | `ntk pull` | Download existing theme or theme file | | `ntk push` | Push current theme state to store | | `ntk watch` | Watch for local changes and automatically push changes to store | | `ntk sass` | Process sass to css, see [Sass Processing](#sass-processing) | #### Browse Store Themes [#browse-store-themes] To see what themes exist on the store, run `ntk list` to print the theme ID and name of each, with the active theme marked. ```bash ntk list ``` Output looks like: ``` [development] Available themes: [development] [42] Spring Launch [development] [43] Holiday Promo (Active) ``` If you do not have a `config.yml`, also pass `--apikey` and `--store`. #### Work on an Existing Theme [#work-on-an-existing-theme] To start working on a theme that already exists on the store, `ntk checkout` downloads it into your directory and writes `config.yml` with the theme ID. ```bash ntk checkout --theme_id= ``` `--theme_id` / `-t` is required. If you do not have a `config.yml`, also pass `--apikey` and `--store`: ```bash ntk checkout --theme_id= --apikey="" --store="https://{store}.29next.store" ``` `ntk checkout` differs from `ntk pull` in one way: `checkout` writes `config.yml` so the directory is ready for subsequent `ntk push` / `ntk watch` runs; `pull` downloads the same files without writing `config.yml`. #### Add a New Theme to the Store [#add-a-new-theme-to-the-store] `ntk init` registers your current directory as a new theme on the store and writes a `config.yml`. It does not download or scaffold any files — run it inside an existing theme codebase, then `ntk push` to upload the files. > **Warning:** Building a theme from an empty directory is not advised. Start from a copy of a complete theme — the [Spark](https://github.com/NextCommerceCo/spark) starter theme (Tailwind CSS), the older Bootstrap-based [Intro Bootstrap](https://github.com/NextCommerceCo/intro-bootstrap) or an existing theme from your store via [`ntk checkout`](#work-on-an-existing-theme). ```bash ntk init --name="" ``` `--name` / `-n` is required. If you do not have a `config.yml` yet, also pass `--apikey` and `--store`: ```bash ntk init --name="" --apikey="" --store="https://{store}.29next.store" ``` On success, `ntk init` logs the new theme ID and name, and persists the theme ID into `config.yml` so subsequent commands can omit `--theme_id`. #### Sync Files to the Store [#sync-files-to-the-store] To sync files between your local directory and the store, use `ntk push` to upload and `ntk pull` to download. Both upload or download the whole theme by default, and both accept file paths as positional arguments to limit the operation to specific files. > **Note:** File paths are relative to the theme root. `ntk push` only uploads files inside the theme directories (`assets`, `checkout`, `configs`, `layouts`, `locales`, `partials`, `sass`, `templates`) with valid theme file extensions — a path outside of them is skipped silently, not reported as an error. **Push a single file** ```bash ntk push templates/index.html ``` **Push a subset of files** ```bash ntk push templates/index.html assets/main.css ``` **Pull a single file** ```bash ntk pull templates/index.html ``` **Pull a subset of files** ```bash ntk pull templates/index.html assets/main.css ``` #### Watch for File Changes [#watch-for-file-changes] `ntk watch` monitors your theme directory and automatically pushes changed files to the store. Use it while you develop — save a file and the change is uploaded moments later. ```bash ntk watch ``` On start, `ntk watch` logs the store, theme ID, a preview-theme URL, and the directory it is watching. Press `Ctrl + C` to stop. > **Note (Verify Changes on the Network Domain):** After each upload, open the affected route on `https://{store}.29next.store` and verify the served HTML, expected assets, and behavior. Do not use a mapped public storefront domain to decide whether a push succeeded. Check the public domain only after network-domain verification. > **Warning:** Deletes sync too — deleting a local file while `ntk watch` is running deletes that file from the theme on the store. > **Note:** `ntk watch` watches the current directory tree (subdirectories included) and only uploads files with valid theme extensions. It does not accept file arguments. To scope changes to specific files, run `ntk push` with file paths instead. #### Sass Processing [#sass-processing] Theme kit includes support for Sass processing via [Python Libsass](https://sass.github.io/libsass-python/). Sass processing includes support for variables, imports, nesting, mixins, inheritance, custom functions, and more. > **Warning:** Sass processing is only supported on local, files in the `sass` directory are uploaded to your store for storage but cannot be edited in the store theme editor. **How it works** 1. Put `scss` files in top level `sass` directory. 2. Run `ntk sass` or `ntk watch` to process theme `sass` files. 3. Top level `scss` files will be processed to `css` files in the asset directory with the same name. **Example Theme with Sass Structure** ```text title="Sass Processing" ├── assets │   ├── main.css // reference this asset file in templates ├── sass │   ├── _base.scss │   ├── _variables.scss │   └── main.scss // processed to assets/main.css ``` # Theme Settings (https://developers.nextcommerce.com/docs/storefront/themes/settings) ### Introduction [#introduction] Theme settings are the power behind the dashboard theme editor experience allowing users to customize the look and feel of their storefront without needing to know how to code. ### Theme Settings Location [#theme-settings-location] Theme settings consist of two JSON files in the `/configs` directory. ``` configs └── settings_schema.json └── settings_data.json ``` * `settings_schema.json` - Used to create the settings schema to create settings shown in the dashboard theme editor. * `settings_data.json` - Used to store theme settings values for access in templates for rendering. ### Using Settings in Templates [#using-settings-in-templates] Settings are passed to templates settings context variable allowing you to access settings values by their name. See example below of changing the layout by conditionally adding a class based on a radio setting. **Settings Schema** ```json title="Using Settings in Templates" { "General": { "Settings": [ { "name": "store_name", "label": "Store Name", "help_text": "Public name of your store.", "type": "text", "max_length": 250, "required": 1, "default": "Store Name" }, { "name": "layout", "label": "Layout Style", "help_text": "Control the layout style.", "type": "radio", "options": [ { "name": "Boxed", "value": "boxed" }, { "name": "Full Width", "value": "full" } ], "default": "boxed" } ] } } ``` **Template** ```html title="Template"

Welcome to {{ settings.store_name }}!

Conditionally boxed or full width content
``` ### Attribute Reference [#attribute-reference] | Attribute | Required | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------- | | `type` | Yes | Type of form input, see Schema Input Types. | | `name` | Yes | Name of the setting and key for access in template settings object variable. | | `label` | Yes | Theme settings form input label. | | `help_text` | No | Theme settings form input help text that shows below the input. | | `required` | No | JSON boolean, accepts true or false, false by default. | | `default` | No | Default value for the setting. | | `options` | No | List of `key:value` pairs for options. Applicable to radio and select field types for their choices. | | `max_length` | No | Applicable to `text` field types to limit the length of text input. | | `max_value` | No | Applicable to number field types to limit the max value. | | `min_value` | No | Applicable to number field types to set a min value. | ## Schema Input Types [#schema-input-types] Schema input types map to input fields that will be rendered in the settings form in the dashboard. ### checkbox [#checkbox] A `checkbox` setting outputs a checkbox field input for use cases such as toggling features on and off. When accessing a `checkbox` field value in a template, it returns boolean. ```json title="Example checkbox setting" { "name": "enable_cookie_msg", "label": "Enable Cookie Message Pop", "help_text": "Enable cookie message to site visitors.", "type": "checkbox", "default": true } ``` ### color [#color] A `color` setting outputs a color picker field allowing the user to chose a color for cases such as customizing font and button styles. When accessing a `color` field value in a template, it returns a hex value as a string. ```json title="Example color setting" { "type": "color", "name": "btn_primary_color", "label": "Primary Button Color", "help_text": "Primary color for buttons.", "default": "" } ``` ### css [#css] A `css` setting outputs an css code editor allowing the user to add custom css in a nicely formatted editor for cases such as custom css styles. When accessing a `css` field value in a template, it returns the a string with the css pre-wrapped with `` tags. ```json title="Example css setting" { "type": "css", "name": "custom_css", "label": "Custom CSS", "help_text": "Example css input." } ``` ### email [#email] A `email` setting outputs a email field allowing the user to add validated email text for cases of showing a contact email. When accessing a `email` field value in a template, it returns a string. ```json title="Example email setting" { "type": "email", "name": "contact_email_address", "label": "Public contact email address.", "help_text": "Email to show in site footer.", "default": "" } ``` ### file [#file] A `file` setting outputs a file upload input field allowing the user to upload files for scenarios such as a homepage banner background image. When accessing a `file` field value in a template, it returns a full CDN link to the uploaded file. ```json title="Example file setting" { "type": "file", "name": "logo", "label": "Store Logo", "help_text": "Primary logo used throughout the site.", "default": "uploads/logo.png" } ``` ### html [#html] A `html` setting outputs an html code editor allowing the user to add custom html in a nicely formatted editor for cases such as code snippets or video embeds. When accessing a `html` field value in a template, it returns the html content string. ```json title="Example html setting" { "type": "html", "name": "custom_html", "label": "Custom HTML", "help_text": "Example html input." } ``` ### image\_picker [#image_picker] An `image_picker` setting outputs outputs an image picker modal making all uploaded image assets available to select from. ```json title="Example image picker setting" { "name": "example_image_picker", "label": "Example Image Field", "type": "image_picker", "required": false } ``` ### menu [#menu] A `menu` setting field outputs a dropdown select field to choose from the available navigation menus. When accessing a `menu` setting value in a template, it returns a menu object allowing you to access the menus items. See [menus](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#menus) object details for working with menus in templates. ```json title="Example menu setting" { "name": "header_menu", "label": "Header Menu", "type": "menu", "required": true, "help_text": "Header Menu" } ``` ### multi-select [#multi-select] A `multi-select` setting outputs a multi-select field that can allows users to select multiple values from a predefined list of options. When accessing a `multi-select` field value in a template, it returns a list of values that have been saved. ```json title="Example multi-select setting" { "type": "select", "multi-select": true, "name": "accepted_payment_methods", "label": "Accepted Payment Methods", "help_text": "Control which payment methods are shown.", "options": [ { "name": "Visa", "value": "visa" }, { "name": "Master Card", "value": "mastercard" }, { "name": "American Express", "value": "amex" }, { "name": "Paypal", "value": "paypal" }, { "name": "Klarna", "value": "klarna" } ], "default": [ "visa", "mastercard" ] } ``` ### number [#number] A `number` setting field outputs a standard number input field allowing users to input a number respecting the optionally available min and max values. When accessing a `number` setting value in a template, it's returned as an integer. ```json title="Example number setting" { "type": "number", "name": "homepage_testimonials_count", "label": "Homepage Number Testimonials to Show", "help_text": "Control the number of homepage testimonials to show", "max_value": 10, "min_value": 0, "default": 3 } ``` ### page [#page] A `page` setting field outputs a dropdown select field to choose from the available pages. When accessing a `page` setting value in a template, it returns a page object. See [page](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#page) object details for working with pages in templates. ```json title="Example page setting" { "type": "page", "name": "page_setting", "label": "Example Setting Page", "help_text": "Link to page configured from settings." } ``` ### product [#product] A `product` setting field outputs a dropdown select field to choose from the available products in the store catalogue. When accessing a `product` setting value in a template, it returns a product object. See [product](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#product) object details for working with products in templates. ```json title="Example product setting" { "name": "hero_product", "label": "Hero Product", "type": "product", "help_text": "Hero product on homepage banner.", "required": false } ``` ### products [#products] A `products` setting field outputs a multi-select field to choose from the available products in the store catalogue. When accessing a `products` setting value in a template, it returns a list of products to iterate through. See [product](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#product) object details for working with products in templates. ```json title="Example products setting" { "name": "featured_products", "label": "Featured Products", "type": "products", "help_text": "Featured products for homepage." } ``` ### product\_category [#product_category] A `product_category` setting field outputs a dropdown select field to choose from the available product categories. When accessing a `product_category` setting value in a template, it returns a product category object. See [product category](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#product_category) object details for working with product categories in templates. ```json title="Example featured_products setting" { "name": "featured_products", "label": "Featured Product Category", "type": "product_category", "help_text": "Featured product category for homepage" } ``` ### product\_categories [#product_categories] A `product_categories` setting field outputs a multi-select field to choose from the available product categories. When accessing a `product_categories` setting value in a template, it returns a list of product categories to iterate through. See [product category](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#product_category) object details for working with product categories in templates. ```json title="Example product_categories setting" { "name": "top_product_categories", "label": "Top Product Categories", "type": "product_categories", "help_text": "Featured product categories for homepage" } ``` ### radio [#radio] A `radio` setting outputs a radio option field that can be used in option selection scenarios such as alignment or layout style. When accessing a `radio` setting value in a template, it's returned as a string. ```json title="Example radio setting" { "type": "radio", "name": "layout", "label": "Layout Style", "help_text": "Control the layout style.", "options": [ { "name": "Boxed", "value": "boxed" }, { "name": "Full Width", "value": "full" } ], "default": "boxed" } ``` ### range [#range] A `range` setting outputs a slider that can be used to a varying numerical value such as font size, number of columns or opacity. When accessing a `range` setting value in a template, it's returned as an integer. ```json title="Example range setting" { "type": "range", "name": "slider_range_field", "label": "Headings font size", "help_text": "", "min": 0, "max": 50, "step": 1, "unit": "px", "required": true, "default": 25 } ``` ### richtext [#richtext] A `richtext` setting displays a WYSIWYG editor with basic text formatting options allowing users to input and format text content. When accessing a `richtext` setting value in a template, it's returned as a string. ```json title="Example richtext setting" { "type": "richtext", "name": "richtext_content", "label": "Description Content", "help_text": "Example rich text input field." } ``` ### select [#select] A `select` setting outputs a dropdown option select field that can be used in option selection scenarios such as alignment or layout style. When accessing a `select` setting value in a template, it's returned as a string. ```json title="Example select setting" { "type": "select", "name": "header_style", "label": "Header Style", "help_text": "Choose header layout style.", "options": [ { "name": "Full Width", "value": "full" }, { "name": "Boxed", "value": "boxed" }, { "name": "Overlay", "value": "overlay" } ], "default": "full" } ``` ### text [#text] A `text` setting outputs a single line input field that can be used in scenarios such as the banner heading text on the homepage. When accessing a `text` setting value in a template, it's returned as a string. ```json title="Example text setting" { "type": "text", "name": "store_name", "label": "Store Name", "help_text": "Public name for your store.", "max_length": 250, "required": true, "default": "My New Store Name" } ``` ### textarea [#textarea] A `textarea` setting outputs a multi-line textarea input field that can be used in scenarios such as the banner sub-heading text on the homepage. When accessing a `textarea` setting value in a template, it's returned as a string. ```json title="Example textarea setting" { "type": "textarea", "name": "description", "label": "Description", "help_text": "Example input textarea", "default": "Example long multi-line sub-heading text for the banner." } ``` ### url [#url] A `url` setting outputs an input url type field that accepts fully qualified urls that can be used for relative paths or external url links. When accessing a `url` setting value in a template, it's returned as a string. ```json title="Example url setting" { "type": "url", "name": "social_link", "label": "Social Media Link", "help_text": "Link to your social media page.", "default": "" } ``` # Translations (https://developers.nextcommerce.com/docs/storefront/themes/translations) Theme templates can be fully localized with translations so that your store visitors are shown content in their local language. Use the t (translation) tag in your templates to access string translations in the locale files. Learn more about the [t tag](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#t) and theme Locale files. ### Using Translations in Practice [#using-translations-in-practice] The `t` tag accepts an initial argument that is the key reference to the translation string in a locale file. **Template** ```jinja title="Using Translations in Practice"

{% t 'customer.orders.order_history' %}

``` **en.default.json** ```json title="en.default.json" { "orders": { "order_history": "Order History" } } ``` **Result** ```html title="Result"

Order History

``` ### Passing Variable Arguments to Translations [#passing-variable-arguments-to-translations] You can pass multiple named arguments to translations through the `t` tag used in the translation. **Template** ```jinja title="Passing Variable Arguments to Translations"

{% t 'customer.profile.welcome_msg' with name=request.user.first_name %}

``` **en.default.json** ```json title="Passing Variable Arguments to Translations" { "customer": { "welcome_message": "Hi {{ name }}, welcome to your account!" } } ``` **Result** ```html title="Result"

Hi John, welcome to your account!

``` ### Pluralization Support [#pluralization-support] The t tag accepts two arguments for pluralization: * Pass the count argument with a value for cardinal pluralization, ie 1, 2, 3, 4. * Pass the index argument with a value for ordinal pluralization, ie First, Second, Third, Forth. Pluralization rules follow [Unicode CLDR](https://github.com/unicode-org/cldr) specification, available keys include: * `one` * `other` * `two` * `zero` * `few` * `many` #### Cardinal Pluralization [#cardinal-pluralization] Cardinal pluralization can be used to display different translations based on the value passed to `count`. **Template** ```jinja title="Cardinal Pluralization"

{% t 'customer.notifcations.total' with count=notification.count %}

``` **en.default.json** ```json title="Cardinal Pluralization" { "customer": { "notifcations": { "one": "You have {{ count }} notification.", "other": "You have {{ count }} notifications.", "zero": "You don't have any notifications." } } } ``` **Result** ```html title="Result"

You have 1 notification.

You have 3 notifications.

You don't have any notifications.

``` #### Ordinal Pluralization [#ordinal-pluralization] Ordinal pluralization can be used to display different translations based on the index value to display the ordering of an object. **Template** ```jinja title="Ordinal Pluralization"

{% t 'customer.orders.orders_msg' with index=customer.orders.count %}

``` **en.default.json** ```json title="Ordinal Pluralization" { "customer": { "orders_msg": { "one": "Congrats on your {{ index }}st order!", "two": "Congrats on your {{ index }}nd order!", "few": "Congrats on your {{ index }}rd order!", "other": "Congrats on your {{ index }}th order!" } } } ``` **Result** ```html title="Result"

Congrats on your 1st order!

Congrats on your 2nd order!

Congrats on your 3rd order!

Congrats on your 4th order!

``` # Storefront CDN & Caching (https://developers.nextcommerce.com/docs/storefront/themes/cdn-and-caching) Storefront leverages CDNs and many caching strategies to ensure fast performant user experiences for your end customers. > **Note (Use Network Domain while Building Themes):** Always use the store network domain `https://{store}.29next.store` when developing, previewing, debugging, and verifying themes. Do not use a mapped public storefront domain to decide whether a theme change landed. ### Asset CDN [#asset-cdn] All merchant uploaded media assets and theme assets are loaded from our CDN for the fastest performance. * **Media** - Links to uploaded media should always use the `cdn.29next.store` * **Theme Assets** - Theme assets should use the [asset\_url](https://developers.nextcommerce.com/docs/storefront/themes/templates/filters#asset_url) in templates which always generates a full CDN link on the storefront. ### Full Page Caching [#full-page-caching] All pages on storefront are cached for 5 minutes to ensure popular pages are as fast as possible for customers and minimal impact on the overall platform load. * User is Anonymous (unauthenticated). * Domain is a merchant mapped domain. * Page is not dynamic, ie `/cart/`, `/checkout/`, `/accounts/` do not use full page caching. > **Tip (Verify the Network Domain First):** Confirm the latest theme on `https://{store}.29next.store` first. After it is correct there, check the mapped public storefront domain as a final customer-path smoke test. ### Template Caching [#template-caching] Themes use many templates ie `layouts`, `partials`, and `assets` that when compiled together create amazing customer experiences. Templates are cached in memory to reduce database queries when compiling templates into the full html response. Updating a template through the dashboard or [Theme Kit](https://developers.nextcommerce.com/docs/storefront/themes/theme-kit) should automatically purge the cache for you to see your latest changes on the network domain, see notes above. > **Warning:** There are a few cases wherein a form on the frontend needs to use a `{% csrf_token %}` field to secure submission to the backend. The platform core JS will automatically replace `{% csrf_token %}` that are in cached versions of pages to ensure the forms still work. > > **It is advisable to not implement custom templates that require `{% csrf_token %}`, we recommend the [Storefront GraphQL API](https://developers.nextcommerce.com/docs/storefront/graphql) instead.** # Templates (https://developers.nextcommerce.com/docs/storefront/themes/templates) ### Introduction [#introduction] The storefront theme templates language is designed to be both powerful and easy to use. If you have any exposure to working with other text-based template languages such as Jinja2 or Liquid, you should feel right at home. > **Tip:** The storefront theme template system provides **tags, filters** and **variables** for control flow logic inside of a template. ### Variables [#variables] Variables look like this: `{{ variable }}` and contain the content the template uses to render to the page. Variables contain a dictionary structure of content and use . notation to access attributes. **Template** ```jinja title="Variables"

Hello {{ customer.name }} !

``` **Result** ```html title="Variables"

Hello John!

``` ### Filters [#filters] Filters allow you to modify the output of a variables and look like this `{{ customer.name|title }}`. This would display the value of `{{ customer.name }}` after being filtered there the title filter to make format the string to title case. See built-in filter reference. **Template** ```jinja title="Filters"

Hello {{ customer.name|title }}!

``` **Result** ```html title="Result"

Hello John!

``` ### Tags [#tags] Tags can do many things such as control flow, iterations, template inheritance, and theme translations. Tags look like this `{% tag %}` . [See built-in tag reference](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags). **Template** ```jinja title="Tags"
    {% if products %} {% for product in products %}
  • {{ product.title }}
  • {% endfor %} {% else %}
  • No products found.
  • {% endif %}
``` **Result** ```html title="Result"
  • Product A
  • Product B
  • Product C
``` Using these core building blocks you can create fully customized shopping experiences for customers. # Object Reference (https://developers.nextcommerce.com/docs/storefront/themes/templates/objects) Objects are template variables you can use to dynamically populate templates in your storefront theme. See documentation below and details of available template objects and their properties. ## Global Objects [#global-objects] Global objects are available across all templates and pages enabling theme developers to create dynamic custom pages powered by the store data. ### currencies [#currencies] Returns a list of active storefront currencies you can iterate over, see [currency](#currency). ```jinja title="Example Storefront Change Currency Form" {% if currencies and currencies|length > 1 %}
  • {% csrf_token %}
  • {% endif %} ``` ### languages\_active\_storefront [#languages_active_storefront] Returns a list of active storefront languages you can iterate over, see [language](#language). ```jinja title="Example Storefront Change Language Form" {% if languages_active_storefront %}
  • {% csrf_token %}
  • {% endif %} ``` ### menus [#menus] Allows you to access a menu's items by its code to iterate over to generate a menu from the backend, see [menu items](#items-menu). Menus are configured in the dashboard at **Storefront > Navigation**. ```jinja title="Example Dynamic Menu" {% for item in menus.header_menu.items %} {% if item.level > 0 %} {% else %} {% endif %} {% endfor %} ``` > **Tip:** Storefront Menus can be up to 3 levels, ensure your custom menu supports 2 nested menu item levels, see child and grandchild above. ### products [#products] Returns a list of products you can iterate over, see [product](#product). ```jinja title="Example Storefront Products Query and Loop" {% where products 'title' 'contains' 'featured' as products_filtered %} {% for product in products_filtered %}
    {% with image=product.primary_image %} {% image_thumbnail image.original "350x350" crop="center" upscale=True as thumb %} {{ product.get_title }} {% endwith %}
    {% purchase_info_for_product request product as session %} {% if session.price.exists %} {{ session.price.price|currency:session.price.currency }} {% else %}
    {% endfor %} ``` ### product\_categories [#product_categories] Returns a list of product categories you can iterate over, see [product\_category](#product_category). ```jinja title="Example Storefront Product Categories Query and Loop" {% where product_categories 'id' 'exact' 1 as homepage_categories %} {% for category in homepage_categories %}
    {% with image=category.image %} {% if image %} {% image_thumbnail image "350x350" crop="center" upscale=True as thumb %} {{ category.name }} {% endif %} {% endwith %}
    {{ category.name }}
    {{ category.description|safe }} Shop now
    {% endfor %} ``` ### posts [#posts] Returns a list of blog posts you can iterate over, see [post](#post). ```jinja title="Example Storefront Recent Blog Posts Query and Loop" {% for post in posts|slice:"3" %}
    {% with image=post.featured_image %} {% if image %} {% image_thumbnail image "400x250" upscale=False crop="top" as thumb %} {{ post.title }} {% endif %} {% endwith %}
    {{post.posted_date|date:"M d, Y"}}
    {{ post.content|striptags|truncatechars_html:140 }}
    {% endfor %} ``` ### post\_categories [#post_categories] Returns a list of post categories you can iterate over, see [post\_category](#post_category). ```jinja title="Example Storefront Post Categories Query and Loop" ``` ### privacy\_policy [#privacy_policy] Content from store Privacy Policy settings, typically used in a "Privacy Policy" page to automatically pull content in from settings. > **Tip:** Store policies are configured in the dashboard at **Settings > Policies**. The content entered there is automatically available as global template variables. ```jinja title="privacy_policy" {{ privacy_policy }} ``` ### request [#request] The current session active request context. ```jinja title="Example Request Object Usage" ``` | Property | Type | Description | | --------------- | ------ | ------------------------------------------------- | | `get_host` | String | Current host domain. | | `path` | String | Current url path. | | `COUNTRY_CODE` | String | Current active geo country code, see [geo](#geo). | | `CURRENCY_CODE` | String | Current active currency code. | | `LANGUAGE_CODE` | String | Current active language code. | ### settings [#settings] Theme settings object with stored theme settings values as properties. See [theme settings](https://developers.nextcommerce.com/docs/storefront/themes/settings) docs. ```jinja title="Example Colors Styles From Theme Settings" ``` ### store [#store] Returns the store object with general information about the store and the contact details. ```jinja title="Example Store Object Usage"
    {{ store.legal_name }}
    {{ store.address.line_1 }}
    {% if store.address.line_2 %}{{ store.address.line_2 }}
    {% endif %} {{ store.address.city }}, {{ store.address.state }} {{ store.address.postcode }}
    {{ store.address.country }}

    Email Support

    ``` | Property | Type | Description | | ---------------------- | ------ | ----------------------------------------------------- | | `address` | object | The store address object see [address](#address). | | `branding` | object | The store branding object, see [branding](#branding). | | `name` | String | General name of the store defined in settings. | | `tagline` | String | Store tagline defined in settings. | | `legal_name` | String | Legal name of the store. | | `phone` | String | Store phone number. | | `email` | String | Store email address. | | `timezone` | String | Store timezone. | | `schema` | String | Store schema, ie the store network subdomain. | | `get_meta_title` | String | Store SEO meta title. | | `get_meta_description` | String | Store SEO meta description. | ### storefront\_geos [#storefront_geos] Returns a list of configured markets, see [geos](#geo). ```jinja title="Example Storefront Geo Switcher" {% if storefront_geos and storefront_geos|length > 1 %} {% endif %} ``` ### subscription\_terms\_and\_conditions [#subscription_terms_and_conditions] Content from store subscription terms and conditions settings, typically used in a "Subscription Terms & Conditions" page to automatically pull content in from settings. ```jinja title="subscription_terms_and_conditions" {{ subscription_terms_and_conditions }} ``` ### terms\_and\_conditions [#terms_and_conditions] Content from store terms and conditions settings, typically used in a "Terms & Conditions" page to automatically pull content in from settings. ```jinja title="terms_and_conditions" {{ terms_and_conditions }} ``` ## Objects [#objects] Object have many properties that can be accessed in templates. ### address [#address] | Property | Type | Description | | ---------- | ------ | ----------------- | | `line_1` | String | Address line 1. | | `line_2` | String | Address line 2. | | `city` | String | Address City. | | `state` | String | Address State. | | `postcode` | String | Address Postcode. | | `country` | String | Address Country. | ### branding [#branding] Store branding properties accessed through the [store](#store) object to leverage within templates. Branding values are configured in the dashboard at **Settings > Branding**. | Property | Type | Description | | --------------- | ------ | -------------------------------------------------------- | | `logo` | File | Store branding logo, use `.url` to access the file link. | | `icon` | File | Store branding icon, use `.url` to access the file link. | | `primary_color` | String | Store branding primary color, returns a HEX code. | | `accent_color` | String | Store branding accent color, returns a HEX code. | ### currency [#currency] Currency object accessed through [currencies](#currencies). | Property | Type | Description | | -------- | ------ | ----------------------------------- | | `code` | String | Name of the currency, ie `USD`. | | `symbol` | String | The symbol of the currency, ie `$`. | ### country [#country] Country object to access properties about a country, see [storefront\_geos](#storefront_geos) for example usage. | Property | Type | Description | | -------- | ------ | ------------------------------------------------------------------------------------------------- | | `code` | String | Two letter [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) country code. | | `name` | String | Full name of the country. | ### geo [#geo] A `geo` is a combination of a language and currency typically associated with a market, see full example in [storefront\_geos](#storefront_geos). | Property | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------------------------- | | `currency` | object | The currency object, see [currency](#currency). | | `country` | object | The country object, see [country](#country) | | `language` | String | Two letter [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code. | ### image [#image] Product images, see example usage below. ```jinja title="image" {% with all_images=product.get_all_images %} {% for image in all_images %}{% image_thumbnail image.original "100x100" crop="center" as thumb %}
    {{ product.get_title }}
    {% endfor %} {% endif %} {% endwith %} ``` | Property | Type | Description | | ---------- | ------ | ------------------------------------------------------------------ | | `original` | File | The original image file, typically used when creating a thumbnail. | | `url` | String | The full CDN link to render the image. | ### items (menu) [#items-menu] A menu `item` has properties to support creating dynamic menus configured through the dashboard menu editor. ```jinja title="Example Dynamic Menu" {% for item in menus.header_menu.items %} {% if item.level > 0 %} {% else %} {% endif %} {% endfor %} ``` > **Tip:** Storefront Menus can be up to 3 levels, ensure your custom menu supports 2 nested menu item levels, see child and grandchild above. | Property | Type | Description | | --------------- | ------- | ----------------------------------------------------------------------------------- | | `active` | Boolean | Indicates whether the link is currently active. | | `child_active` | Boolean | Indicates whether any child link of the current link is active. | | `child_current` | Boolean | Indicates whether the URL path matches the URL of a child link of the current link. | | `current` | Boolean | Indicates whether the current URL path matches the URL of the link. | | `items` | List | Contains the child items belonging to the current menu item. | | `level` | Integer | Specifies the hierarchical level of the current menu item. | | `name` | String | Represents the display name of the current menu item. | | `url` | String | Denotes the URL path for the menu item's href link. | ### page [#page] Storefront Page object details available in the `pages/page.html` template and custom page templates. | Property | Type | Description | | ---------------------- | ------ | -------------------------- | | `title` | String | Page title. | | `content` | String | Page content. | | `get_meta_title` | String | Page SEO meta title. | | `get_meta_description` | String | Page SEO meta description. | ### paginator [#paginator] The `paginator` object is available on "list views" where the items to display are paginated from the backend, works in tandem with [page\_obj](#page_obj). ```jinja title="paginator" {% if paginator.num_pages > 1 %}
    {% endif %} ``` | Property | Type | Description | | ----------- | ------- | ---------------------------------- | | `num_pages` | Integer | Number of pages in pagination set. | ### page\_obj [#page_obj] The `page_obj` object is available on "list views" where the items to display are paginated from the backend, works in tandem with [paginator](#paginator). | Property | Type | Description | | ---------------------- | ------- | --------------------- | | `number` | Integer | Current page number. | | `has_next` | Object | Next page object. | | `has_previous` | Object | Previous page. | | `next_page_number` | Integer | Next page number. | | `previous_page_number` | Integer | Previous page number. | ### post [#post] Blog post properties available through global [post](#posts) context and the `blog/post.html` and `blog/index.html` templates. | Property | Type | Description | | ---------------------- | ------ | ----------------------------------------------------------------------------------------------------------------- | | `id` | String | The post ID. | | `featured_image` | File | Blog post featured image file, use `.url` to access full file link. | | `categories` | List | List of related post categories, use `.all` to return a list of categories, see [post\_category](#post_category). | | `get_absolute_url` | String | A full path link to the blog post. | | `title` | String | The post title. | | `content` | String | Post content. | | `slug` | String | Post url slug. | | `get_meta_title` | String | Post SEO meta title. | | `get_meta_description` | String | Post SEO meta description. | ### post\_category [#post_category] Blog post category properties available through global [post\_categories](#post_categories) context and the `blog/post.html` and `blog/index.html` templates. | Property | Type | Description | | ------------------ | ------ | -------------------------------------- | | `id` | String | Blog post category ID. | | `name` | String | Blog post category name. | | `get_absolute_url` | String | Blog post category canonical url link. | ### product [#product] Product configured in the store catalogue. | Property | Type | Description | | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | | `id` | String | Product ID. | | `title` | String | Product title. | | `get_title` | String | Product title. | | `get_all_images` | List | List of product images, see [image](#image). | | `get_description` | String | Product description. | | `sku` | String | Product stock keeping unit (sku). | | `categories` | List | List of product categories, use `.all` to return a list of categories, see [product\_category](#product_category). | | `parent` | Object | Parent product if product is variant (child). | | `is_child` | Boolean | Product structure indicating this is a variant product. | | `primary_image` | File | Primary image of the product, use `.url` to access a full file link. | | `get_absolute_url` | String | Product canonical url link. | | `num_approved_reviews` | Integer | Count of approved product reviews. | | `rating` | Integer | Product rating as a number between 1 and 5. | | `reviews` | Integer | List of product reviews, see [review](#review). | | `get_meta_title` | String | Product SEO meta title. | | `get_meta_description` | String | Product SEO meta description. | ### product\_category [#product_category] Product category object. | Property | Type | Description | | ---------------------- | ------ | ------------------------------------------------ | | `id` | String | Category ID. | | `name` | String | Category name. | | `description` | String | Category description. | | `image` | File | Category image, use `.url` to access image link. | | `get_absolute_url` | String | Category canonical url link. | | `get_meta_title` | String | Category SEO meta title. | | `get_meta_description` | String | Category SEO meta description. | ### price [#price] Product price object. | Property | Type | Description | | -------------- | ------ | --------------------------- | | `currency` | String | Price currency. | | `price` | String | Price that will be charged. | | `price_retail` | String | Suggested retail price. | ### review [#review] Product review object. | Property | Type | Description | | -------- | ------- | ------------------------------------- | | `id` | String | Review ID. | | `title` | String | Review title. | | `score` | Integer | Review score. | | `user` | Object | The customer that created the review. | ### voucher [#voucher] Voucher object. | Property | Type | Description | | -------- | ------ | -------------- | | `title` | String | Voucher title. | | `code` | String | Voucher code. | ## View-Specific Objects [#view-specific-objects] View-specific objects are available in the templates rendered by their corresponding views. See [Template Contexts](#template-contexts) below for which objects are available in each template. ### session [#session] The session object is returned by the [`purchase_info_for_product`](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#purchase_info_for_product) and [`purchase_info_for_line`](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#purchase_info_for_line) template tags. It contains pricing and availability information for a product in the current user's currency. ```jinja title="Example Product Price with Session" {% purchase_info_for_product request product as session %} {% if session.price.exists %} {% if session.price.price_retail %} {{ session.price.price_retail|currency:session.price.currency }} {% endif %} {{ session.price.price|currency:session.price.currency }} {% endif %} {% if not session.availability.is_available_to_buy %} Out of Stock {% endif %} ``` **session.price** | Property | Type | Description | | -------------- | ------- | ---------------------------------------- | | `exists` | Boolean | Whether a price exists for this product. | | `price` | Decimal | The current selling price. | | `price_retail` | Decimal | The retail/compare-at price, if set. | | `currency` | String | The currency code for this price. | | `excl_tax` | Decimal | The price excluding tax. | **session.availability** | Property | Type | Description | | --------------------- | ------- | ------------------------------------- | | `is_available_to_buy` | Boolean | Whether the product can be purchased. | ### variant\_form [#variant_form] The variant selection form object available on product detail pages (`catalogue/product.html`). Used to render variant attribute selectors (size, color, etc.) for products with variants. ```jinja title="Example Variant Selection" {% if variant_form %}
    {% for field in variant_form %}
    {% render_field field class+="form-select" %}
    {% endfor %}
    {% endif %} ``` ### filters [#filters] The `filters` object is a list of product filter/facet objects available on category pages (`catalogue/category.html`). Filters allow customers to narrow product listings by attributes like price, color, size, etc. The `has_active_filter` boolean indicates whether any filter is currently applied. There are three filter types: `price_range`, `boolean`, and `list`. Each type has different properties for rendering the appropriate UI. ```jinja title="Example Category Filters" {% if filters %}
    {% for filter in filters %}
    {{ filter.label }}
    {% if filter.type == 'price_range' %} {% elif filter.type == 'boolean' %} {% else %} {% for value in filter.values %} {% endfor %} {% endif %}
    {% endfor %} {% if has_active_filter %} {% for filter in filters %} {% for active in filter.active_values %} Remove {{ active }} {% endfor %} {% endfor %} {% endif %}
    {% endif %} ``` **Common filter properties:** | Property | Type | Description | | --------------- | ------ | ------------------------------------------------------- | | `type` | String | Filter type: `'price_range'`, `'boolean'`, or `'list'`. | | `label` | String | Display label for the filter. | | `active_values` | List | List of currently selected filter values. | | `url_to_remove` | String | URL to remove this active filter. | **Price range filter properties:** | Property | Type | Description | | ---------------------- | ------- | ------------------------------------- | | `min_value.value` | Decimal | Current minimum price value. | | `min_value.param_name` | String | Query parameter name for the minimum. | | `min_value.label` | String | Display label for the minimum input. | | `max_value.value` | Decimal | Current maximum price value. | | `max_value.param_name` | String | Query parameter name for the maximum. | | `max_value.label` | String | Display label for the maximum input. | | `range_max` | Decimal | Maximum possible range value. | **Boolean filter properties:** | Property | Type | Description | | ------------------------ | ------- | ----------------------------------------- | | `true_value.param_name` | String | Query parameter name for true selection. | | `true_value.active` | Boolean | Whether true is currently selected. | | `true_value.count` | Integer | Number of results matching true. | | `false_value.param_name` | String | Query parameter name for false selection. | | `false_value.active` | Boolean | Whether false is currently selected. | | `false_value.count` | Integer | Number of results matching false. | **List filter value properties:** | Property | Type | Description | | ------------ | ------- | ----------------------------------------- | | `param_name` | String | Query parameter name for this value. | | `active` | Boolean | Whether this value is currently selected. | | `count` | Integer | Number of results matching this value. | | `value` | String | The filter value. | | `label` | String | Display label for this value. | ### line [#line] Cart line object available on the cart page (`templates/cart.html`) through the `formset` context. Each form in the formset exposes its cart line as `form.instance`. ```jinja title="Example Cart Line Properties" {% for form in formset %} {% with line=form.instance %} {% for property in line.properties %}
    {{ property.key }}: {{ property.value }}
    {% endfor %} {% endwith %} {% endfor %} ``` | Property | Type | Description | | ------------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `properties` | List | Line item properties captured on the product page, see [Personalized Products](https://developers.nextcommerce.com/docs/storefront/themes/guides/personalized-products). Property names starting with an underscore are excluded. | **line.properties** | Property | Type | Description | | -------- | ------ | --------------------------------------------------------------------------------- | | `key` | String | The property name, taken from the `properties[]` input on the product page. | | `value` | String | The value submitted by the customer. | ## Template Contexts [#template-contexts] All templates receive the [Global Objects](#global-objects) (`store`, `settings`, `currencies`, `languages_active_storefront`, `menus`, `products`, `product_categories`, `posts`, `post_categories`, `privacy_policy`, `terms_and_conditions`, `subscription_terms_and_conditions`, `request`, `storefront_geos`). The table below lists additional view-specific context variables passed to each template. | Template | View-Specific Context | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `templates/index.html` | Global objects only. Use the [`where`](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#where) tag to query products and categories. | | `templates/cart.html` | `formset` (cart line forms, each `form.instance` is a [line](#line)) | | `templates/catalogue/product.html` | `product`, `variant_form`, `interval_count_choices` | | `templates/catalogue/index.html` | `products` (paginated), `paginator`, `page_obj` | | `templates/catalogue/category.html` | `category`, `products` (paginated), `filters`, `has_active_filter`, `paginator`, `page_obj` | | `templates/search.html` | `query`, `products` (paginated), `paginator`, `page_obj` | | `templates/blog/index.html` | `posts` (paginated), `paginator`, `page_obj` | | `templates/blog/post.html` | `post` | | `templates/pages/page.html` | `page` | | `templates/support/index.html` | `categories` | | `templates/support/category.html` | `category`, `articles` | | `templates/support/article.html` | `article` | | `templates/reviews/index.html` | `product`, `reviews` (paginated), `paginator`, `page_obj` | | `templates/reviews/form.html` | `product`, `form` | | `templates/reviews/review.html` | `product`, `review` | > **Tip:** Use the [`purchase_info_for_product`](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#purchase_info_for_product) tag in any template to get pricing and availability for a product. Use [`cart_form`](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#cart_form) on product pages to generate add-to-cart forms. > **Warning:** **Cart and user data must use the [Storefront GraphQL API](https://developers.nextcommerce.com/docs/storefront/graphql).** All storefront pages are fully cached per language and currency combination. Per-user data (cart contents, authentication state, wishlists) rendered in server-side templates would be cached and served to other visitors. Use client-side JavaScript with the GraphQL API for all cart and user interactions. ## Dashboard Cross-Reference [#dashboard-cross-reference] Some template variables are populated from dashboard settings. Use this reference to understand where data originates when building or debugging templates. | Template Variable | Dashboard Path | Description | | ----------------------------------- | ------------------------------- | ------------------------------------------- | | `store.branding.logo` | Settings > Branding | Store logo image. | | `store.branding.icon` | Settings > Branding | Store icon/favicon image. | | `store.branding.primary_color` | Settings > Branding | Primary brand color (HEX). | | `store.branding.accent_color` | Settings > Branding | Accent brand color (HEX). | | `store.name`, `store.tagline` | Settings > General | Store name and tagline. | | `store.legal_name`, `store.address` | Settings > General | Legal details and address. | | `menus.{menu_key}.items` | Storefront > Navigation | Navigation menu items (up to 3 levels). | | `privacy_policy` | Settings > Policies | Privacy policy content. | | `terms_and_conditions` | Settings > Policies | Terms and conditions content. | | `subscription_terms_and_conditions` | Settings > Policies | Subscription T\&C content. | | `settings.*` | Storefront > Themes > Customize | Theme settings from `settings_schema.json`. | # Tag Reference (https://developers.nextcommerce.com/docs/storefront/themes/templates/tags) Template tags enable theme developers to include and extend templates and blocks, add logical operators, query and filter data, and much more. See all available template tags below. ### app\_asset\_url [#app_asset_url] The `app_asset_url` tag is used to reference asset files included in app snippets. ```jinja title="app_asset_url" ``` ### app\_hook [#app_hook] The `app_hook` tag specifies a theme storefront location Apps can inject snippets into to extend storefront templates from Apps. Theme developers should ensure their templates include all available `app_hooks` to ensure compatibility with all Apps. ```jinja title="app_hook" {% app_hook 'global_header' %} ``` **Available `app_hook` locations include:** ### add\_query\_param [#add_query_param] The `add_query_param` tag appends or updates a query parameter on the current URL. Commonly used for building pagination links and filter URLs while preserving existing query parameters. ```jinja title="add_query_param" {% add_query_param request 'page' page_obj.next_page_number %} ``` ```jinja title="Example Pagination with add_query_param" {% if paginator.num_pages > 1 %} {% endif %} ``` | Argument | Description | | ----------- | --------------------------------------------- | | request | The current `request` context object. | | param\_name | The query parameter name to set, eg `'page'`. | | value | The value to assign to the parameter. | ### annotate\_form\_field [#annotate_form_field] The `annotate_form_field` tag adds HTML attributes to a form field based on its Django form field properties (required, type, etc.). Useful for adding client-side validation and accessibility attributes. ```jinja title="annotate_form_field" {% annotate_form_field field %} {{ field }} ``` ### boolean operators [#boolean-operators] If tags may be used in combination with boolean operators for conditional control flow. | Operator | Description | | -------- | ---------------------------- | | `and` | multiple conditions are true | | `or` | either condition is true | | `not` | a condition is not true | | `in` | contained within | | `not in` | a condition is not true | | `is` | two values are the same | | `is not` | two values are not the same | | `==` | equality | | `!=` | inequality | | `<` | less than | | `>` | greater than | | `<=` | less than or equal to | | `>=` | greater than or equal to | ### cart\_form [#cart_form] The `cart_form` tag generates an add-to-cart form for a product. Required on every product page to enable purchasing. ```jinja title="cart_form" {% cart_form request product 'single' as cart_form %} ``` The tag returns a form object, not rendered HTML. Loop over it to render each field in your own markup, as the Intro Bootstrap theme does through its `partials/form_fields.html` partial. ```jinja title="templates/catalogue/product.html" {% block product_cart_form %}
    {% purchase_info_for_product request product as session %} {% if session.availability.is_available_to_buy %} {% cart_form request product 'single' as cart_form %}
    {% csrf_token %} {% include "partials/form_fields.html" with form=cart_form breakpoint_col='col-3 col-md-2' size='lg' nolabel=True %}
    {% else %}
    {% t "store.catalogue.out_of_stock" %}
    {% endif %}
    {% endblock %} ``` ```jinja title="partials/form_fields.html" {% if form.is_bound and not form.is_valid %} {% endif %} {% if form.non_field_errors %} {% for error in form.non_field_errors %} {% endfor %} {% endif %} {% for field in form %} {% include 'partials/form_field.html' with field=field style=style %} {% endfor %} ``` | Argument | Description | | -------------- | --------------------------------------------- | | request | The current `request` context object. | | product | The `product` context object. | | quantity\_type | Accepts `'single'` or `'multiple'`. | | variable | Assigned template variable name for the form. | Add [line item property](https://developers.nextcommerce.com/docs/storefront/themes/guides/personalized-products) inputs to this form to capture customer personalization such as an engraving or gift message. ### comment [#comment] Ignores everything between `{% comment %}` and `{% endcomment %}`. An optional note may be inserted in the first tag. For example, this is useful when commenting out code for documenting why the code was disabled. ```jinja title="comment"

    Rendered text with {{ pub_date|date:"c" }}

    {% comment "Optional note" %}

    Commented out text with {{ create_date|date:"c" }}

    {% endcomment %} ``` ### core\_js [#core_js] The `core_js` tag outputs the platform's core JavaScript bundle. This is required in every theme's base layout and powers cart functionality, AJAX form submissions, CSRF token handling, and other platform features. ```jinja title="core_js" {% core_js %} ``` ```jinja title="Example Placement in Base Layout" {# jQuery must be loaded before core_js #} {% core_js %} ``` > **Warning:** jQuery must be loaded before `{% core_js %}`. The platform's core JavaScript depends on jQuery being available in the global scope. ### csrf\_token [#csrf_token] This tag is used for CSRF protection and required on any template with a form that sends a POST request to the back end. ```jinja title="csrf_token"
    {% csrf_token %}
    ``` ### extends & block [#extends--block] Extends and block tags allow you to define blocks of content in a base template that can be overridden by templates that extend from it for template inheritance. **Parent Template** ```jinja title="layouts/base.html" {% block title %}My amazing store{% endblock %}
    {% block content %}{% endblock %}
    ``` **Child Template** ```jinja title="templates/blog.html" {% extends "layouts/base.html" %} {% block title %}My Blog Post Title{% endblock %} {% block sidebar %} {% endblock %} {% block content %}

    My Blog Post Title...

    {% endblock %} ``` ### for [#for] Loops over each item in array, making the item available in a context variable. For example, to display a list of products provided in the `{{ products }}` variable. ```jinja title="for"
      {% for each in products %}
    • {{ each.get_title }} - Rating {{ each.rating }} stars
    • {% endfor %}
    ``` ### if, elif, & else [#if-elif--else] Use the if tag to evaluate if a variable is "true" and control the contents displayed. ```jinja title="if, elif, & else" {% if products_list > 2 %}

    Number of products: {{ products_list|length }}

    {% elif products_list %}

    We only have a single product now.

    {% else %}

    Sorry, there are no products.

    {% endif %} ``` ### include [#include] Loads a template and renders it with the current context. This is a way of including other templates within a template. ```jinja title="include" {% include "partials/footer.html" %} ``` > **Warning:** A word of caution, multi-level inclusion inside of iterative loops can create performance penalties while rendering a page html for site visitors. Use **includes** sparingly when working inside iterative loops. ### image\_thumbnail [#image_thumbnail] The `image_thumbnail` tag is used to resize images dynamically in templates. The tag accepts arguments that control how the image is resized. ```jinja title="image_thumbnail" {% with image=line.product.primary_image %} {% image_thumbnail image.original "200x200" upscale=False as thumb %} {{ product.get_title }} {% endwith %} ``` | Argument | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | size | Example, 100x100 (widthxheight), sets the desired image size in pixels. If width and height are given the image is rescaled to maximum values of height and width given. Aspect ratio preserved. | | crop | This option is only used if both width and height is given. Crop behaves much like css background-position. The image is first rescaled to minimum values of height and width given, this will be equivalent to the padding box in the above text. | | upscale | Upscale is a boolean and controls if the image can be upscaled or not. For example if your source is 100x100 and you request a thumbnail of size 200x200 and upscale is False this will return a thumbnail of size 100x100. If upscale was True this would result in a thumbnail size 200x200 (upscaled). The default value is `True`. | | quality | Quality is a value between 0-100 and controls the thumbnail write quality. Default value is `95`. | | progressive | This controls whether to save jpeg thumbnails as progressive jpegs. Default value is `True`. | | orientation | This controls whether to orientate the resulting thumbnail with respect to the source EXIF tags for orientation. Default value is `True`. | | format | This controls the write format and thumbnail extension. Formats supported by the shipped engines are 'JPEG' and 'PNG'. Default value is `JPEG`. | | padding | Padding is a boolean and controls if the image should be padded to fit the specified geometry. | ### now [#now] Displays the current date and/or time, using a format according to the given string. [See available date reference for format options](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date). ### purchase\_info\_for\_product [#purchase_info_for_product] The `purchase_info_for_product` tag is used to retrieve the price of a product in the current user session's currency. ```jinja title="purchase_info_for_product" {% purchase_info_for_product request product as session %} {% if session.price.exists %} {{ session.price.price|currency:session.price.currency }} {% else %} ``` | Argument | Description | | -------- | ----------------------------------------------- | | request | Must pass the current `request` context object. | | product | Must pass the current `product` context object. | ### purchase\_info\_for\_line [#purchase_info_for_line] The `purchase_info_for_line` tag retrieves the price and availability of a cart line item in the current session's currency. Works the same as `purchase_info_for_product` but accepts a cart line object. ```jinja title="purchase_info_for_line" {% purchase_info_for_line request line as session %} {% if session.price.exists %} {{ session.price.price|currency:session.price.currency }} {% endif %} ``` | Argument | Description | | -------- | ----------------------------------------------- | | request | Must pass the current `request` context object. | | line | Must pass a cart `line` context object. | ### render\_field [#render_field] The `render_field` tag renders a form field with additional HTML attributes. Use it to add CSS classes, placeholders, and other attributes to Django form fields in templates. ```jinja title="render_field" {% render_field field class+="form-control" placeholder="Enter your email" %} ``` ```jinja title="Example Form with render_field"
    {% csrf_token %}
    {% render_field field class+="form-control" %} {% if field.errors %}
    {{ field.errors.0 }}
    {% endif %}
    ``` | Argument | Description | | ---------- | -------------------------------------------------------------------------------- | | field | The form field object to render. | | attributes | HTML attributes to add, using `attr="value"` or `attr+="value"` (append) syntax. | ### seo [#seo] The `seo` tag generates SEO meta data for products in standardized format for consumption by 3rd party systems. ```jinja title="seo" {% seo %} ``` The tag is expected to be added to the top of product details template to generate necessary SEO meta data. ### t [#t] The `t` (translation) tag is used to display localized content from a theme's translations files. The t tag accepts a key and additional replacement variable arguments to access the theme translations and return the language appropriate string for display to the user. ```jinja title="t" {% t 'customer.orders.order_count' with count=orders.count %} ``` ### url [#url] Returns an absolute url path reference matching a given view with parameters. See the URL & Template Path reference for a list of all URL names to use with the `{% url %}` template tag. ```jinja title="url" Blog ``` ### where [#where] Queries and filters store objects to dynamically assign objects to a variable. ```jinja title="where" {% where {{ objects }} '{{ field_name }}' '{{ lookup_expr }}' '{{ filter_query }}' as {{ variable }} %} ``` | Argument | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | objects | The global context object. Supported objects include; [`products`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#products), [`product_categories`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#product_categories), [`posts`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#posts), [`post_categories`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#post_categories), [`currencies`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#currencies), [`storefront_geos`](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#storefront_geos) | | field\_name | Object field name to perform the lookup on. | | lookup\_expr | Query lookup expression; `exact` or `contains`. | | filter\_query | Your custom query filter. | | variable | Assigned template variable name, ie `featured_products`. | ```jinja title="Example Usage" {% where products 'title' 'contains' 'watch' as watches %} ``` # Filter Reference (https://developers.nextcommerce.com/docs/storefront/themes/templates/filters) ## Arrays & Lists [#arrays--lists] ### dictsort [#dictsort] Takes a list of dictionaries and returns that list sorted by the key given in the argument. ```jinja title="dictsort" {{ value|dictsort:"name" }} ``` For example: ```jinja title="dictsort" {{ value|dictsort:"category" }} ``` If value is: ```json title="dictsort" [ {'category': 'B', 'count': 4}, {'category': 'C', 'count': 22}, {'category': 'A', 'count': 12} ] ``` then the output would be: ```json title="dictsort Output" [ {'category': 'A', 'count': 12}, {'category': 'B', 'count': 4}, {'category': 'B', 'count': 4} ] ``` ### dictsortedreversed [#dictsortedreversed] Takes a list of dictionaries and returns that list sorted in reverse order by the key given in the argument. This works exactly the same as the above filter, but the returned value will be in reverse order. ### first [#first] Returns the first item in a list. ```jinja title="first" {{ value|first }} ``` For example, if value is the list \['a', 'b', 'c'], the output will be 'a'. ### join [#join] Joins a list with a string. ```jinja title="join" {{ value|join:" - " }} ``` For example, if value is the list \['a', 'b', 'c'], the output will be the string "a - b - c". ### last [#last] Returns the last item in a list. ```jinja title="last" {{ value|last }} ``` For example, if value is the list \['a', 'b', 'c', 'd'], the output will be the string "d". ### slice [#slice] Returns a slice of the list. ```jinja title="slice" {{ some_list|slice:":2" }} ``` For example, if some\_list is \['a', 'b', 'c'], and slice to first 2, the output will be \['a', 'b']. ### unordered\_list [#unordered_list] Recursively takes a self-nested list and returns an HTML unordered list – WITHOUT opening and closing `
      ` tags. The list is assumed to be in the proper format. ```jinja title="unordered_list" {{ value|unordered_list }} ``` For example, if var contains `['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]`, then `{{ var|unordered_list }}` would return: ```jinja title="unordered_list"
    • States
      • Kansas
        • Lawrence
        • Topeka
      • Illinois
    • ``` ## Default [#default] ### default [#default-1] If value evaluates to False, uses the given default. Otherwise, uses the value. ```jinja title="default" {{ value|default:"something" }} ``` For example, If value is "" (an empty string), the output will be something. ### default\_if\_none [#default_if_none] If (and only if) value is None, uses the given default. Otherwise, uses the value. Note that if an empty string is given, the default value will not be used. Use the default filter if you want to fallback for empty strings. ```jinja title="default_if_none" {{ value|default_if_none:"something" }} ``` For example, if value is None, the output will be something. ## Format [#format] ### date [#date] Formats a date according to the given format. ```jinja title="date" {{ value|date:"D d M Y" }} ``` For example, `{{ value|date:"D d M Y" }}` this would convert a date to this format output will be the string Wed 09 Jan 2020. See available date reference for format options. ### escape [#escape] Escapes a string’s HTML. Specifically, it makes these replacements: \< is converted to \<> is converted to >' (single quote) is converted to '" (double quote) is converted to "& is converted to & ```jinja title="escape" {{ title|escape }} ``` ### escapejs [#escapejs] Escapes characters for use in JavaScript strings. This does not make the string safe for use in HTML or JavaScript template literals, but does protect you from syntax errors when using templates to generate JavaScript/JSON. ```jinja title="escapejs" {{ value|escapejs }} ``` For example, if value is escaping, the output will be \u003Cb\u003Eescaping\u003C/b\u003E ### json\_script [#json_script] Safely outputs a variable object as JSON, wrapped in a ` ``` ### make\_list [#make_list] Returns the value turned into a list. For a string, it’s a list of characters. For an integer, the argument is cast to a string before creating a list. ```jinja title="make_list" {{ value|make_list }} ``` ## Internationalization [#internationalization] ### language\_name\_local [#language_name_local] Returns a localized name of the language. ```jinja title="language_name_local" {{ LANGUAGE_CODE|language_name_local }} ``` For example, if the value is `fr`, the output would be `Français`. ## Integers [#integers] ### divisibleby [#divisibleby] Returns True if the value is divisible by the argument. ```jinja title="divisibleby" {{ value|divisibleby:"3" }} ``` For example, if value is 21, the output would be True. ### floatformat [#floatformat] Allows you to specify the number of decimal places to format a float to. ```jinja title="floatformat" {{ value|floatformat:2 }} ``` For example, if value is 34.2342 and use floatformat:2, the output will be 34.23. ## HTML [#html] ### linebreaks [#linebreaks] Replaces line breaks in plain text with appropriate HTML; a single newline becomes an HTML line break `
      ` and a new line followed by a blank line becomes a paragraph break `

      `. ```jinja title="linebreaks" {{ value|linebreaks }} ``` For example, if value is Joel\nis a slug, the output will be `

      Joel
      is a slug

      `. ### linebreaksbr [#linebreaksbr] Converts all newlines in a piece of plain text to HTML line breaks `
      `. If value is Sandy is a slug, the output will be `Sandy
      is a slug`. ```jinja title="linebreaksbr" {{ value|linebreaksbr }} ``` ### truncatewords\_html [#truncatewords_html] Similar to truncatechars, except that it is aware of HTML tags. Any tags that are opened in the string and not closed before the truncation point are closed immediately after the truncation. ```jinja title="truncatewords_html" {{ value|truncatechars_html:7 }} ``` For example, if value is `

      Sandy is a slug

      `, the output will be `

      Sandy i…

      `. ## Strings [#strings] ### capfirst [#capfirst] Capitalizes the first character of the value. ```jinja title="capfirst" {{ value|capfirst }} ``` For example, if the first character is not a letter, this filter has no effect. For example, if value is chicago, the output will be Chicago. ### cut [#cut] Removes all values of arg from the given string. ```jinja title="cut" {{ value|cut:" " }} ``` For example, if value is String with spaces, the output will be Stringwithspaces. ### length [#length] Returns the length of the value. This works for both strings and lists. ```jinja title="length" {{ value|length }} ``` For example, if value is `['a', 'b', 'c', 'd']` or "abcd", the output will be 4. ### length\_is [#length_is] Returns True if the value’s length is the argument, or False otherwise. ```jinja title="length_is" {{ value|length_is:"4" }} ``` For example, if value is \['a', 'b', 'c', 'd'] or "abcd", the output will be True. ### linenumbers [#linenumbers] Displays text with line numbers. ```jinja title="linenumbers" {{ value|linenumbers }} ``` For example, if value is: ```jinja title="linenumbers" one two three ``` the output will be: ```jinja title="linenumbers Output" 1. one 2. two 3. three ``` ### lower [#lower] Converts a string into all lowercase. ```jinja title="lower" {{ value|lower }} ``` For example, if value is Totally LOVING this Product!, the output will be totally loving this product!. For example, if value is the string "Sandy", the output would be the list \['S', 'a', 'n', 'd', 'y']. If value is 123, the output will be the list \['1', '2', '3']. ### pluralize [#pluralize] Returns a plural suffix if the value is not 1, '1', or an object of length 1. By default, this suffix is 's'. ```jinja title="pluralize" You have {{ num_messages }} message{{ num_messages|pluralize }} ``` ### split [#split] Splits a string by the given delimiter and returns a list. ```jinja title="split" {{ value|split:"," }} ``` For example, if value is `"red,green,blue"`, the output will be the list `['red', 'green', 'blue']`. ### slugify [#slugify] Converts to ASCII. Converts spaces to hyphens. Removes characters that aren’t alphanumerics, underscores, or hyphens. Converts to lowercase. Also strips leading and trailing whitespace. ```jinja title="slugify" {{ value|slugify }} ``` For example, if value is "Sandy is a slug", the output will be "sandy-is-a-slug". ### title [#title] Converts a string into titlecase by making words start with an uppercase character and the remaining characters lowercase. This tag makes no effort to keep “trivial words” in lowercase. ```jinja title="title" {{ value|title }} ``` For example, if value is "my FIRST post", the output will be "My First Post". ### truncatewords [#truncatewords] Truncates a string after a certain number of words based on the argument. ```jinja title="truncatewords" {{ value|truncatewords:2 }} ``` For example, if value is "Sandy is a slug", the output will be "Sandy is …". ### upper [#upper] Converts a string into all uppercase. ```jinja title="upper" {{ value|upper }} ``` For example, if value is "Sandy is a slug", the output will be "SANDY IS A SLUG". ### urlencode [#urlencode] Escapes a value for use in a URL. ```jinja title="urlencode" {{ value|urlencode }} ``` For example, if value is "[https://www.example.org/](https://www.example.org/)", the output will be "https%3A%2F%2Fwww\.example.org%2F". ### wordcount [#wordcount] Returns the number of words. ```jinja title="wordcount" {{ value|wordcount }} ``` For example, if value is "Joel is a slug", the output will be 4. ## Currency [#currency] ### currency [#currency-1] Formats a decimal value as a currency string using the provided currency code. This is the primary filter for displaying prices throughout a theme. ```jinja title="currency" {{ session.price.price|currency:session.price.currency }} ``` For example, if the price is `29.99` and the currency is `USD`, the output will be `$29.99`. The filter handles currency symbol placement and formatting based on the currency code. ```jinja title="Example Product Price Display" {% purchase_info_for_product request product as session %} {% if session.price.exists %} {% if session.price.price_retail %} {{ session.price.price_retail|currency:session.price.currency }} {% endif %} {{ session.price.price|currency:session.price.currency }} {% endif %} ``` ## Files [#files] ### asset\_url [#asset_url] The asset\_url filter can be applied to theme asset files to generate CDN link to the asset for loading in the template HTML. The file argument is relative to the assets directory of the theme. ```jinja title="asset_url" {{ 'style.css'|asset_url }} ``` ## Math [#math] ### add [#add] Adds the argument to the value. ```jinja title="add" {{ value|add:2 }} ``` For example, if value is 4, then the output will be 6. The filter will try to force both values to integers. If this fails, it’ll attempt to add the values together anyway. If it fails, the result will be an empty string. ### abs [#abs] Returns the absolute value of a number. ```jinja title="abs" {{ value|abs }} ``` For example, if value is -3, the output would be 3. The filter will return the absolute value. ### atleast [#atleast] Limits a number to a minimum value. ```jinja title="atleast" {{ value|atleast:5 }} ``` For example, if the value is 3, the filter would return 5. ### atmost [#atmost] Limits a number to a maximum value. ```jinja title="atmost" {{ value|atmost:5 }} ``` For example, if the value is 7, the filter would return 5. ### ceil [#ceil] Rounds a number up to the nearest integer. ```jinja title="ceil" {{ value|ceil }} ``` For example, if the value was 1.2, the filter would return 2. ### dividedby [#dividedby] Divides a number by a given number. ```jinja title="dividedby" {{ value|dividedby:3 }} ``` For example, if the value was 9, the filter would return 3. ### floor [#floor] Rounds a number down to the nearest integer. ```jinja title="floor" {{ value|floor }} ``` For example, if the value was 1.2, the filter would return 1. ### minus [#minus] Subtracts a given number from another number. ```jinja title="minus" {{ value|minus:2.5 }} ``` For example, if the value was 5, the filter would return 2.5. ### modulo [#modulo] Returns the remainder of dividing a number by a given number. ```jinja title="modulo" {{ value|modulo:5 }} ``` For example, if the value was 12, the filter would return 2. ### plus [#plus] Adds two numbers. ```jinja title="plus" {{ value|plus:5 }} ``` For example, if the value was 5, the filter would return 10. ### round [#round] Rounds a number to the nearest integer. ```jinja title="round" {{ value|round }} ``` For example, if the value was 2.7, the filter would return 3. ### times [#times] Multiplies a number by a given number. ```jinja title="times" {{ value|times:3 }} ``` For example if the value was 2, the filter would return 9. # URLs & Template Paths (https://developers.nextcommerce.com/docs/storefront/themes/templates/urls-and-template-paths) Use the URL and Template Path reference below when building your theme to ensure that your templates load for the correct storefront URL paths. > **Warning:** Ensure your template paths match with expected template paths for built-in storefront views. Use the public themes on [Github](https://github.com/NextCommerceCo/) as a reference guide and starting point. All URL paths are automatically localized to the users language following your store's Localization settings. ### Homepage [#homepage] | URL Name | URL Path | Template Path | | -------- | -------- | -------------------- | | N/A | / | templates/index.html | ### Blog [#blog] | URL Name | URL Path & Arguments | Template Path | | ---------------- | ------------------------- | ------------------------- | | blog:blog-list | /blog/ | templates/blog/index.html | | blog:blog-detail | /blog/detail/:post\_slug/ | templates/blog/post.html | ### Cart [#cart] | URL Name | URL Path | Template Path | | -------------------- | ---------------------------------------- | --------------------------------- | | cart:summary | /cart/ | templates/cart.html | | cart:add | POST /cart/add/:product\_slug/ | N/A (action endpoint) | | cart:saved | /cart/saved/ | templates/cart.html (saved items) | | cart:vouchers-add | POST /cart/vouchers/add/ | N/A (action endpoint) | | cart:vouchers-remove | POST /cart/vouchers/remove/:voucher\_id/ | N/A (action endpoint) | ### Catalogue [#catalogue] | URL Name | URL Path & Arguments | Template Path | | ------------------ | ------------------------------------ | --------------------------------- | | catalogue:index | /catalogue/ | templates/catalogue/index.html | | catalogue:category | /catalogue/category/:category\_slug/ | templates/catalogue/category.html | | catalogue:detail | /catalogue/:product\_slug/ | templates/catalogue/product.html | ### Checkout [#checkout] | URL Name | URL Path & Arguments | Template Path | | ------------------------- | -------------------- | ---------------------- | | checkout:shipping-address | checkout/\* | checkout/checkout.html | ### Pages [#pages] | URL Name | URL Path & Arguments | Template Path | | -------- | -------------------- | ------------------------- | | N/A | /:page\_slug | templates/pages/page.html | ### Reviews [#reviews] | URL Name | URL Path & Arguments | Template Path | | ------------------------ | -------------------------------------- | ------------------------------ | | catalogue:reviews-list | /catalogue/:product\_slug/reviews/ | templates/reviews/index.html | | catalogue:reviews-detail | /catalogue/:product\_slug/reviews/:id/ | templates/reviews/review\.html | | catalogue:reviews-add | /catalogue/:product\_slug/reviews/add/ | templates/reviews/form.html | ### Search [#search] | URL Name | URL Path | Template Path | | ------------- | -------- | --------------------- | | search:search | /search/ | templates/search.html | ### Support [#support] | URL Name | URL Path | Template Path | | ---------------------- | ------------------------------------ | ------------------------------- | | support:category-list | /support/categories/ | templates/support/index.html | | support:article-list | /support/categories/:category\_slug/ | templates/support/category.html | | support:article-detail | /support/articles/:article\_slug/ | templates/support/article.html | ### Customer / Authentication [#customer--authentication] | URL Name | URL Path | Template Path | | ------------------------------ | ------------------------- | ---------------------- | | customer:login | /accounts/login/ | N/A (platform-managed) | | customer:logout | /accounts/logout/ | N/A (platform-managed) | | customer:summary | /accounts/ | N/A (platform-managed) | | customer:support-ticket-create | /accounts/support/create/ | N/A (platform-managed) | ### Localization [#localization] These are POST action endpoints used in forms for switching language, currency, or storefront geo. | URL Name | Method | Description | | ------------------- | ------ | --------------------------------------------------------------- | | set\_language | POST | Change the active language. | | core:set-currency | POST | Change the active currency. | | core:set-storefront | POST | Change the active storefront geo (country, language, currency). | ### API [#api] | URL Name | URL Path | Description | | --------------------- | ------------- | ------------------------------------------------------------ | | storefrontapi:graphql | /api/graphql/ | [Storefront GraphQL API](https://developers.nextcommerce.com/docs/storefront/graphql) endpoint. | ### Error Pages [#error-pages] | URL Name | URL Path | Template Path | | -------- | --------------- | ------------------ | | N/A | /:any-403-error | templates/403.html | | N/A | /:any-404-error | templates/404.html | | N/A | /:any-500-error | templates/500.html | # Custom Page Templates (https://developers.nextcommerce.com/docs/storefront/themes/guides/custom-page-templates) Pages created in the storefront dashboard (**Storefront > Pages**) can have very diverse design requirements that often require custom layouts. In this guide, we'll go over some of the best practices for creating and managing custom page templates. ### Page Templates Location [#page-templates-location] In the `templates/pages` directory of a theme, theme developers can edit/manage the custom page templates. ```bash title="Page Templates Location" pages └── page.html (default) └── page..html └── page..html ``` ### Extend & Override [#extend--override] **Create a Custom Page Template** Create a new page template in the `templates/pages` directory with the following naming convention: ```bash title="Custom Page Template Naming" templates/pages/page..html ``` Templates that follow this naming convention will be selectable from the page detail area in the storefront dashboard. **Extend & Override Default Page Template** As a best practice, you should [extend](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#extends--block) the default page template to override the necessary [template blocks](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#extends--block) to achieve your customization with a limited amount of duplicate code. ```jinja title="Example Custom Page Template" {% extends "templates/pages/page.html" %} {% block content %} // Custom Page Content Template Code {% endblock %} ``` This strategy will simplify the creation and management of custom page templates so you can focus on the customized areas for the new custom product template. **Select Template for Page** On your page of choice, select your newly created template as the **Theme Template** to activate the template for your page in the storefront. After uploading and selecting the template, navigate to that page's full storefront URL on the `https://{store}.29next.store` network domain and verify the served HTML, expected assets, and behavior. Do not use a mapped public storefront domain to decide whether the template change landed. > **Tip:** You are not limited to overriding the existing template blocks in the default page template. You can create and add your own to the default template to overide in your custom template. For example, adding `{% block my_custom_block %}{% endblock %}` to page.html, around any area you wish to customize, will allow you to overide it in the custom template # Custom Product Templates (https://developers.nextcommerce.com/docs/storefront/themes/guides/custom-product-templates) Products can have very diverse design requirements that often require custom layouts. In this guide, we'll go over some of the best practices for creating and managing custom product templates. ### Product Templates Location [#product-templates-location] In the `templates/catalogue` directory of a theme, theme developers can edit/manage the product page templates. ```bash title="Product Templates Location" catalogue └── product.html (default) └── product..html └── product..html ``` ### Extend & Override [#extend--override] **Create a Custom Product Template** Create a new product template in the **templates>catalogue** directory with the following naming convention: ```bash title="Custom Product Template Naming" templates/catalogue/product..html ``` Templates that follow this naming convention will be selectable on the product detail to use on the storefront. **Extend & Override Default Product Template** As a best practice, you should [extend](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#extends--block) the default product template to override the necessary [template blocks](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#extends--block) to achieve your customization with a limited amount of duplicate code. ```jinja title="Example Custom Product Template" {% extends "templates/catalogue/product.html" %} {% block header %} // Custom Product Header Code {% endblock header %} {% block product_description %} // Custom Product Content Template Code {% endblock %} ``` This strategy will simplify the creation and management of custom product templates so you can focus on the customized areas for the new custom product template. **Select Template for Product** On your product of choice, select your newly created template as the Product Template to activate the template on your product in the storefront. After uploading and selecting the template, navigate to that product's full storefront URL on the `https://{store}.29next.store` network domain and verify the served HTML, expected assets, and behavior. Do not use a mapped public storefront domain to decide whether the template change landed. > **Tip:** You are not limited to overriding the existing template blocks in the default product template. You can create and add your own to the default template to overide in your custom template. For example, adding `{% block my_custom_block %}{% endblock %}` to product.html, around any area you wish to customize, will allow you to overide it in the custom template # Product Variants Guide (https://developers.nextcommerce.com/docs/storefront/themes/guides/product-variants) Products with multiple variants are very common, for example, a shirt with 3 colors (Blue, Green, Red) and 4 sizes (S,M,L,XL) would have a total of 12 actual product choices (SKUs). Presenting the variant choices to users can add significant complexity for theme developers to create great user experiences for customers within the catalogue. Let's go over how Products with variants and their attributes can be mapped together in the storefront product details template. ### Variant Attribute Choices [#variant-attribute-choices] The first step to adding variant support is adding the Variant Attribute Selectors in your product template to allow a user to see the variant attribute choices.Looping over the `variant_form` template object provides a path to dynamically creating the variant attribute choice selectors driven by the product configuration. ```jinja title="templates/catalogue/product.html" {% for field in variant_form %} {% if 'attr' in field.id_for_label %} {% include "partials/form_field.html" with field=field %} {% endif %} {% endfor %} ``` > **Tip:** Using the choice fields from the template is entirely optional. Theme developers can create their own custom choice selectors using the `product.data` json object for a more customized user experience. ### Map Choices to Variants [#map-choices-to-variants] With the variant choices now available in the template, it is now necessary to map choices from the `variant_form` choices to variant product IDs. *Accessing all variant product data* Use the `product.data` object in your template to generate a detailed json object of the product images, variant attributes, variant prices, and variant availability. ```jinja title="Map Choices to Variants" {{ product.data|json_script:"product-data" }} ``` Use javascript in your template to map the variant select fields to the available product ID and update the add-to-cart form to add the chosen variant. See full example in our Base Theme. # Product Metadata (https://developers.nextcommerce.com/docs/storefront/themes/guides/product-metadata) Product metadata lets you add custom data for products to use in theme templates. This provides a robust structured way for theme developers to customize products display in the storefront. [See our user guide on adding custom metadata fields](https://docs.nextcommerce.com/docs/build-a-store/technical-settings/metadata-fields-and-tags). ### Template Access [#template-access] Product metadata values are accessible in theme templates through their metadata `key`. ```jinja title="Template Access" {{ product.metadata. }} ``` Values set for the given the product metadata will render in the product template. ### Example [#example] Let's look at an example of adding support for a **Product Tagline** to a product template that can be set on an individual product basis in the dashboard. **Create Tagline Product Metadata Field** In your store Metadata settings, create a new Metadata Definition for your tagline. Set the the **object** to `Product` and **key** to `tagline`. **Add Tagline Attribute Variable to Product Template** In your theme's product template, add the code below to render the tagline by accessing it with a template variable. ```jinja title="Example" {{ product.metadata.tagline }} ``` **Add Tagline Value to Product** In your product metadata settings, add your tagline field with a value to render the your storefront product details. :clap: # Personalized Products Guide (https://developers.nextcommerce.com/docs/storefront/themes/guides/personalized-products) Some products need customer input at the time of purchase — an engraving on a mug, a monogram on a bag, a gift message on a card. This information is captured as **line item properties**: name and value pairs attached to a cart line rather than to the product itself. No variant, SKU, or inventory is required for each possible value. ### Add Property Inputs to the Product Template [#add-property-inputs-to-the-product-template] Property inputs are named `properties[]`, where `` is the label stored with the line. Add them inside the [add-to-cart form](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#cart_form) alongside the fields generated by the `cart_form` tag. The engraving label and input are the only addition to the add-to-cart form. ```jinja title="templates/catalogue/product.html" {% purchase_info_for_product request product as session %} {% if session.availability.is_available_to_buy %} {% cart_form request product 'single' as cart_form %}
      {% csrf_token %} {% include "partials/form_fields.html" with form=cart_form %}
      {% else %} {% t "store.catalogue.out_of_stock" %} {% endif %} ``` Add one input per property. A mug with an engraving and a font choice uses `properties[Engraving]` and `properties[Font]`. ### Display Properties in the Cart [#display-properties-in-the-cart] The cart template receives a `formset` of cart line forms. Each `form.instance` is a [line](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#line) with a `properties` list of `key` and `value` pairs. ```jinja title="partials/cart_line_properties.html" {% for property in properties %} {% if property.value %}
      {{ property.key }}: {{ property.value }}
      {% endif %} {% endfor %} ``` ```jinja title="templates/cart.html" {% for form in formset %} {% with line=form.instance %} {% include "partials/cart_line_properties.html" with properties=line.properties %} {% endwith %} {% endfor %} ``` > **Tip:** Themes that ship their own side cart JavaScript need to render properties there too. Request `properties { key value }` on the cart lines in your side cart query and output them alongside the product title. ### Behavior to Expect [#behavior-to-expect] | Behavior | Detail | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Unique lines | The same product added with different property values creates a separate cart line for each combination. Adding it again with identical values increases the quantity of the existing line. | | Empty values | A property submitted with an empty value does not create a separate line. It is still recorded on the line, so guard your display with `{% if property.value %}`. | | Hidden properties | Property names starting with an underscore, such as `properties[_source]`, are stored but excluded from `line.properties` and from the Storefront GraphQL API. Use them for data that should not be shown to customers. | | Value length | Values longer than 500 characters are truncated to 500 characters. | | Checkboxes | When a checkbox named `properties[...]` changes, the value submitted is `yes` or `no`. | | File uploads | File inputs are not supported. Inputs with `type="file"` are ignored. | | Set once | Properties are captured when the line is added to the cart. There is no update path for changing them afterwards. | ### Add Properties with the Storefront GraphQL API [#add-properties-with-the-storefront-graphql-api] Themes that add to the cart through the [Storefront GraphQL API](https://developers.nextcommerce.com/docs/storefront/graphql) pass properties on each line as a JSON object of name and value pairs. The field is available on [createCart](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/create-cart) and [addCartLines](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/add-cart-lines), and is returned on cart lines as `properties { key value }`. ```json title="addCartLines Variables" { "input": { "cartId": "", "lines": [ { "productPk": 1, "quantity": 1, "properties": { "Engraving": "Alex" } } ] } } ``` > **Warning:** `properties` must be a JSON object. Any other value returns the error `Properties must be a JSON object.` ### Related [#related] - [line Object](https://developers.nextcommerce.com/docs/storefront/themes/templates/objects#line) - [cart_form Tag](https://developers.nextcommerce.com/docs/storefront/themes/templates/tags#cart_form) - [Product Variants Guide](https://developers.nextcommerce.com/docs/storefront/themes/guides/product-variants) - [Storefront GraphQL API](https://developers.nextcommerce.com/docs/storefront/graphql) # Storefront GraphQL API (https://developers.nextcommerce.com/docs/storefront/graphql) Build custom sidecarts, upsell flows, and dynamic storefront experiences with the Storefront GraphQL API. Query products, manage carts, apply vouchers, and handle user accounts — all from your theme's JavaScript or any client-side application. ## API Endpoint [#api-endpoint] ```bash title="Storefront GraphQL Endpoint" https://{store}.29next.store/api/graphql/ ``` All requests must be `POST` with `Content-Type: application/json`. Replace `{store}` with your store's subdomain. ## Authentication [#authentication] The Storefront API is available within the context of your storefront on a storefront domain. Requests made from your theme's JavaScript automatically inherit the user's session — no API keys or tokens needed. > **Note:** External access to the Storefront API (outside of the storefront context) will be available in future iterations. ## Interactive Explorer (GraphiQL) [#interactive-explorer-graphiql] Every store includes a built-in **GraphiQL IDE** — an in-browser tool for writing, validating, and testing GraphQL queries directly against your store's schema. Navigate to `/api/graphql/` on your store to open it. GraphiQL lets you: * **Explore the schema** — browse all available queries, mutations, types, and their fields using the documentation sidebar * **Build queries visually** — use the explorer panel to construct queries by selecting fields, without writing GraphQL by hand * **Test in real-time** — run queries and mutations against your store and see the JSON response instantly * **Validate syntax** — get inline error highlighting and autocomplete as you type ```bash title="Storefront GraphQL Endpoint" https://{store}.29next.store/api/graphql/ ``` This is the fastest way to learn what the API offers and prototype queries before adding them to your theme code. ## API Reference [#api-reference] ### Queries [#queries] * [`cart`](https://developers.nextcommerce.com/docs/storefront/graphql/queries/cart) — Retrieve a cart by ID * [`me`](https://developers.nextcommerce.com/docs/storefront/graphql/queries/me) — Get the current authenticated user * [`product`](https://developers.nextcommerce.com/docs/storefront/graphql/queries/product) — Fetch a single product * [`products`](https://developers.nextcommerce.com/docs/storefront/graphql/queries/products) — Query the product catalog ### Mutations [#mutations] * [`createCart`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/create-cart) — Create a new cart * [`addCartLines`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/add-cart-lines) — Add line items to a cart * [`updateCartLines`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/update-cart-lines) — Update quantities on existing cart lines * [`removeCartLines`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/remove-cart-lines) — Remove line items from a cart * [`emptyCart`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/empty-cart) — Remove all items from a cart * [`addVoucher`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/add-voucher) — Apply a voucher code to a cart * [`removeVoucher`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/remove-voucher) — Remove a voucher from a cart * [`updateCartAttribution`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/update-cart-attribution) — Set UTM and affiliate attribution on a cart * [`updateCartMetadata`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/update-cart-metadata) — Update custom metadata on a cart * [`register`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/register) — Register a new customer account * [`tokenAuth`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/token-auth) — Authenticate and obtain a token * [`verifyToken`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/verify-token) — Verify an authentication token * [`updateAccount`](https://developers.nextcommerce.com/docs/storefront/graphql/mutations/update-account) — Update the current user's account details ## Quick start [#quick-start] Fetch the current cart to build a custom sidecart UI: ```bash title="Example: Query the cart" curl -X POST https://yourstore.29next.store/api/graphql/ \ -H "Content-Type: application/json" \ -d '{ "query": "query Cart($id: ID!) { cart(id: $id) { id numItems totalInclTax currency lines { edges { node { id quantity product { title } linePriceInclTax } } } } }", "variables": { "id": "" } }' ``` ```json title="Response" { "data": { "cart": { "id": "Q2FydE5vZGU6MTIz", "numItems": 2, "totalInclTax": "59.98", "currency": "USD", "lines": { "edges": [ { "node": { "id": "Q2FydExpbmVOb2RlOjE=", "quantity": 1, "product": { "title": "Premium Supplement" }, "linePriceInclTax": "29.99" } } ] } } } } ``` ## Common use cases [#common-use-cases] ### Custom sidecarts [#custom-sidecarts] The most common use of the Storefront API is building custom sidecart experiences. Use `cart` queries to fetch the current cart state and render a fully custom cart drawer with your own markup, animations, and styling. ### Upsells and cross-sells [#upsells-and-cross-sells] Add upsell products to the cart dynamically using mutations like `addCartLines`. Query the product catalog with `products` to find related items, then present them in your sidecart or on product pages. ```graphql title="Add an upsell to the cart" mutation AddUpsell($input: AddCartLinesInput!) { addCartLines(input: $input) { success cart { numItems totalInclTax } } } ``` ### User accounts [#user-accounts] Handle customer registration and authentication directly from your storefront with `register`, `tokenAuth`, and `updateAccount` mutations. ### Vouchers and discounts [#vouchers-and-discounts] Apply and remove voucher codes from carts using `addVoucher` and `removeVoucher` mutations, enabling custom promo code UIs. # Event Tracking (https://developers.nextcommerce.com/docs/storefront/event-tracking) ## Overview [#overview] Event Tracking allows merchants and third-party integrations to subscribe to customer engagement events on your storefront for robust customer behavior tracking. ## Getting Started [#getting-started] To add new custom Event Trackers, in your store go to Settings > Tracking Events > Add Event Tracker. ```javascript title="Example Product Added to Cart Event" analytics.subscribe("product_added_to_cart", event => { console.log(event); }); ``` Go to your storefront and add a product to your cart, you'll now see data from your event tracker logged in the console. :tada: ## Including External Scripts [#including-external-scripts] Event trackers are pure javascript, meaning third-party event tracking scripts sometimes need some adjustment before they can be added. ```html title="Original Google Analytics HTML Script Tag" ``` Below is the equivalent expressed as a javascript function to create and append the script tag to the document head. ```javascript title="Converetd Javascript Script Tag" (function() { var script = document.createElement('script'); script.async = true; script.src = "https://www.googletagmanager.com/gtag/js?id=G-EXAMPLE"; document.head.appendChild(script); })(); ``` We can now use this to include the Google Analytics javascript in our event tracker. ## Init [#init] Init is utility method to produce context of the current request/session with details of a `cart`, `store`, or `user` as a JSON object. Use the init method to add context to event tracking integrations. **Init Cart (init.cart)** Use `init.cart` to load context of the current cart data as a JSON object. ```javascript console.log(init.cart); ```
      Init Cart Data
      ```javascript { "id": 1000, "status": "open", "lines": [ { "id": 1000, "product_id": 111, "sku": "TIMELESS-WATCH", "product_title": "Timeless Watch", "product_image": "https://d36qjeq4w.cloudfront.net/media/..../product.jpg", "product_url": "https://amazingwidgets.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "Black Band", "quantity": 2, "currency": "USD", "price_excl_tax": "45.76", "price_incl_tax": "45.76", "total_discount": "0.00", "is_upsell": false, "interval": null, "interval_count": null, "metadata": {} } ], "abandoned": true, "total_incl_tax": "45.76", "total_excl_tax": "45.76", "total_discount": "0.00", "currency": "USD", "user": { "id": 1000, "email": "johndoe@gmail.com", "first_name": "John", "last_name": "Doe", "ip": "123.123.123.123", "phone_number": "+12706814477", "date_joined": "2023-03-15T06:27:46.253558-04:00", "language": "en", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36...." }, "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "checkout_url": "https://amazingwidgets.com/accounts/complete-order/549167as8232c/", "created_at": "2025-03-18T05:27:46.765627-04:00", "metadata": {} } ```
      **Init Store (init.store)** Use `init.store` to load context of the store public data as a JSON object. ```javascript console.log(init.store); ```
      Init Store Data
      ```json { "name": "Amazing Widgets", "tagline": "Amazing Widget and Deals", "timezone": "US/Eastern", "contact_address": { "company_name": "Amazing Widgets LLC", "line1": "2200 Western Pl W", "line2": "", "postcode": "42304", "city": "Hop Top", "state": "KY", "country": "US", "phone_number": "(270) 686-4455" }, "primary_domain": "amazingwidgets.com", "tax_id": "", "available_languages": [ { "code": "en", "label": "English" }, { "code": "fr", "label": "Français" } ], "available_currencies": [ { "code": "USD", "label": "$ USD" }, { "code": "CAD", "label": "CA$ CAD" } ] } ```
      **Init User (init.user)** Use `init.user` to load context of the current authenticated user as a JSON object. ```javascript console.log(init.user); ```
      Init User Data
      ```json { "id": 1000, "email": "johndoe@gmail.com", "first_name": "John", "last_name": "Doe", "orders_count": 29, "phone_number": "+12706814477", "language": "en" } ```
      ## Context [#context] All events have `context` of the parent frame to easily access contextual data about where and how the event occurred. | Property | Description | Reference | | ----------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `document` | Data from the top-frame `document` object (e.g., title, referrer). | [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document) | | `navigator` | Data from the top-frame `navigator` object (e.g., userAgent, language). | [Navigator](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) | | `window` | Data from the top-frame `window` object (e.g., innerWidth, location). | [Window](https://developer.mozilla.org/en-US/docs/Web/API/Window) | ```json title="Example conext object" { "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window } } ``` ## Available Tracking Events [#available-tracking-events] ### page\_viewed [#page_viewed] ```javascript analytics.subscribe("page_viewed", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "page", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": {}, "event_type": "page_viewed", "timestamp": "2024-12-09T06:42:47.750998+00:00", "event_version": "2024-04-01" } ```
      ### product\_category\_viewed [#product_category_viewed] ```javascript analytics.subscribe("product_category_viewed", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "products", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": [ { "id": 111, "url": "https://examplestore.com/catalogue/timeless-watch_111/", "title": "Timeless Watch", "slug": "timeless-watch", "images": [ { "id": 629, "attachment": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "caption": "", "display_order": 0, "product": 111, "variants": [112, 113] } ], "purchase_info": { "availability": "available", "subscription": { "currency": "USD", "price": "29.99", "format": "$29.99" }, "price": { "currency": "USD", "price": "29.99", "format": "$29.99" }, "price_retail": { "currency": "USD", "price": "39.99", "format": "$39.99" } }, "structure": "parent" } ], "event_type": "product_category_viewed", "timestamp": "2024-12-09T06:44:29.959402+00:00", "event_version": "2024-04-01" } ```
      ### product\_viewed [#product_viewed] ```javascript analytics.subscribe("product_viewed", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "product", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "id": 111, "url": "https://examplestore.com/catalogue/timeless-watch_111/", "title": "Timeless Watch", "slug": "timeless-watch", "is_discountable": true, "is_public": true, "ranking": 10, "categories": [ { "id": 1, "name": "Example Category", "slug": "example-category" } ], "enable_subscription": true, "interval": "day", "interval_counts": [ 30, 60, 90 ], "images": [ { "id": 2, "original": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "caption": "", "display_order": 0, "product": 111 } ], "requires_shipping": true, "recommended_products": [ 3, 47 ], "upc": "", "external_tax_code": null, "variant_attributes": [], "variants": [ { "id": 2, "product_id": 111, "title": "Timeless Watch", "sku": "TIMELESS-WATCH-B-BL", "track_stock": true, "allow_backorders": false, "images": [ { "id": 2, "original": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "caption": "", "display_order": 0, "product": 111 } ], "variant_attribute_values": [], "prices": [ { "currency": "USD", "price": "79.99", "retail": "120.00", "subscription": "69.99", "subscription_suggested_downsell": "49.99" } ], "stockrecords": [ { "id": 16, "location_id": 1, "num_in_stock": 9995, "num_allocated": 169, "low_stock_threshold": 500 } ], "unit_cost": "1.00", "date_created": "2017-09-28T08:23:08.368000-04:00", "date_updated": "2024-11-26T03:11:56.747405-05:00", "metadata": { "external_id": "12345" } } ], "rating": 5, "date_created": "2024-01-24T02:02:52.811742-05:00", "date_updated": "2024-10-03T04:32:52.860659-04:00", "metadata": { "excerpt": "
      Product conent.
      ", "special": "Special Promo", "external_id": "12345" } }, "event_type": "product_viewed", "timestamp": "2024-12-09T06:46:18.993008+00:00", "event_version": "2024-04-01" } ```
      ### product\_added\_to\_cart [#product_added_to_cart] ```javascript analytics.subscribe("product_added_to_cart", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "cart_line", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "currency": "USD", "interval": null, "interval_count": null, "is_upsell": false, "price_excl_tax": "79.99", "price_incl_tax": "79.99", "product_id": 111, "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_title": "Timeless Watch", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "quantity": 1, "sku": "TIMELESS-WATCH-B-BL", "total_discount": "0.00", "variant_id": 2, "variant_title": "" }, "event_type": "product_added_to_cart", "timestamp": "2024-12-09T06:46:36.510579+00:00", "event_version": "2024-04-01" } ```
      ### product\_removed\_from\_cart [#product_removed_from_cart] ```javascript analytics.subscribe("product_removed_from_cart", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "cart_line", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "currency": "USD", "interval": null, "interval_count": null, "is_upsell": false, "price_excl_tax": "79.99", "price_incl_tax": "79.99", "product_id": 111, "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_title": "Timeless Watch", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "quantity": 1, "sku": "TIMELESS-WATCH-B-BL", "total_discount": "0.00", "variant_id": 2, "variant_title": "" }, "event_type": "product_removed_from_cart", "timestamp": "2024-12-09T06:47:05.202043+00:00", "event_version": "2024-04-01" } ```
      ### checkout\_started [#checkout_started] ```javascript analytics.subscribe("checkout_started", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "checkout", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "number": null, "status": null, "fulfillment_status": null, "payment_status": null, "is_test": null, "lines": [ { "product_id": 111, "sku": "TIMELESS-WATCH-B-BL", "product_title": "Timeless Watch", "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "", "quantity": 1, "currency": "USD", "price_excl_tax": "79.99", "price_incl_tax": "79.99", "total_discount": "0.00", "is_upsell": false, "interval": null, "interval_count": null } ], "shipping_method": null, "shipping_code": null, "total_incl_tax": "79.99", "total_excl_tax": "79.99", "total_discount": "0.00", "shipping_incl_tax": null, "shipping_excl_tax": null, "total_cost": null, "total_tax": null, "shipping_tax": null, "display_taxes": null, "currency": "USD", "user": null, "shipping_address": null, "billing_address": null, "date_placed": null, "offer_discounts": [], "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "metadata": {}, "transactions": null, "order_status_url": null }, "event_type": "checkout_started", "timestamp": "2024-12-09T06:58:34.676675+00:00", "event_version": "2024-04-01" } ```
      ### checkout\_contact\_info\_submitted [#checkout_contact_info_submitted] ```javascript analytics.subscribe("checkout_contact_info_submitted", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "checkout", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "number": null, "status": null, "fulfillment_status": null, "payment_status": null, "is_test": null, "lines": [ { "product_id": 111, "sku": "TIMELESS-WATCH-B-BL", "product_title": "Timeless Watch", "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "", "quantity": 1, "currency": "USD", "price_excl_tax": "79.99", "price_incl_tax": "79.99", "total_discount": "0.00", "is_upsell": false, "interval": null, "interval_count": null } ], "shipping_method": null, "shipping_code": null, "total_incl_tax": "79.99", "total_excl_tax": "79.99", "total_discount": "0.00", "shipping_incl_tax": null, "shipping_excl_tax": null, "total_cost": null, "total_tax": null, "shipping_tax": null, "display_taxes": null, "currency": "USD", "user": { "id": 123456, "email": "customer@gmail.com", "ip": "182.82.112.1", "first_name": null, "last_name": null, "phone_number": null, "accepts_marketing": true, "language": "en", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" }, "shipping_address": null, "billing_address": null, "date_placed": null, "offer_discounts": [], "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "metadata": {}, "transactions": null, "order_status_url": null }, "event_type": "checkout_contact_info_submitted", "timestamp": "2024-12-09T06:58:34.676675+00:00", "event_version": "2024-04-01" } ```
      ### checkout\_shipping\_address\_submitted [#checkout_shipping_address_submitted] ```javascript analytics.subscribe("checkout_shipping_address_submitted", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "checkout", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "number": null, "status": null, "fulfillment_status": null, "payment_status": null, "is_test": null, "lines": [ { "product_id": 111, "sku": "TIMELESS-WATCH-B-BL", "product_title": "Timeless Watch", "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "", "quantity": 1, "currency": "USD", "price_excl_tax": "79.99", "price_incl_tax": "79.99", "total_discount": "0.00", "is_upsell": false, "interval": null, "interval_count": null } ], "shipping_method": null, "shipping_code": null, "total_incl_tax": "79.99", "total_excl_tax": "79.99", "total_discount": "0.00", "shipping_incl_tax": null, "shipping_excl_tax": null, "total_cost": null, "total_tax": null, "shipping_tax": null, "display_taxes": null, "currency": "USD", "user": { "id": 123456, "email": "customer@gmail.com", "ip": "182.82.112.1", "first_name": "John", "last_name": "Doe", "phone_number": null, "accepts_marketing": true, "language": "en", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" }, "shipping_address": { "id": 7851, "first_name": "John", "last_name": "Doe", "line1": "2200 Western Pl", "line2": "", "line3": "", "line4": "Henderson", "postcode": "42304", "phone_number": "+18123158899", "notes": "", "state": "KY", "country": "US" }, "billing_address": null, "date_placed": null, "offer_discounts": [], "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "metadata": {}, "transactions": null, "order_status_url": null }, "event_type": "checkout_shipping_address_submitted", "timestamp": "2024-12-26T06:46:36.936236+00:00", "event_version": "2024-04-01" } ```
      ### checkout\_shipping\_method\_submitted [#checkout_shipping_method_submitted] ```javascript analytics.subscribe("checkout_shipping_method_submitted", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "checkout", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "number": null, "status": null, "fulfillment_status": null, "payment_status": null, "is_test": null, "lines": [ { "product_id": 111, "sku": "TIMELESS-WATCH-B-BL", "product_title": "Timeless Watch", "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "", "quantity": 1, "currency": "USD", "price_excl_tax": "79.99", "price_incl_tax": "79.99", "total_discount": "0.00", "is_upsell": false, "interval": null, "interval_count": null } ], "shipping_method": "Express 1-2 Days", "shipping_code": "express", "total_incl_tax": "79.99", "total_excl_tax": "79.99", "total_discount": "0.00", "shipping_incl_tax": null, "shipping_excl_tax": null, "total_cost": null, "total_tax": null, "shipping_tax": null, "display_taxes": null, "currency": "USD", "user": { "id": 123456, "email": "customer@gmail.com", "ip": "182.82.112.1", "first_name": "John", "last_name": "Doe", "phone_number": null, "accepts_marketing": true, "language": "en", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" }, "shipping_address": { "id": 7851, "first_name": "John", "last_name": "Doe", "line1": "2200 Western Pl", "line2": "", "line3": "", "line4": "Henderson", "postcode": "42304", "phone_number": "+18123158899", "notes": "", "state": "KY", "country": "US" }, "billing_address": null, "date_placed": null, "offer_discounts": [], "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "metadata": {}, "transactions": null, "order_status_url": null }, "event_type": "checkout_shipping_method_submitted", "timestamp": "2024-12-26T06:46:36.936236+00:00", "event_version": "2024-04-01" } ```
      ### checkout\_completed [#checkout_completed] ```javascript analytics.subscribe("checkout_completed", event => { analytics.debugHelper(event.event_type, event); }); ```
      Event Data
      ```json { "object": "checkout", "context": { "document": {...}, // frame parent document "navigator": {...}, // frame parent navigator "window": {...} // frame parent window }, "data": { "number": "109659", "status": "open", "fulfillment_status": "unfulfilled", "payment_status": "paid", "is_test": true, "lines": [ { "product_id": 111, "sku": "TIMELESS-WATCH-B-BL", "product_title": "Timeless Watch", "product_image": "https://assets.29nex.store/media/demostore/images/products/2021/03/watch.jpg", "product_url": "https://examplestore.com/catalogue/timeless-watch_2/", "variant_id": 2, "variant_title": "", "quantity": 1, "current_quantity": 1, "fulfillable_quantity": 1, "currency": "USD", "price_excl_tax": "79.99", "price_incl_tax": "79.99", "total_discount": "0.00", "unit_cost": "1.00", "total_cost": "1.00", "requires_shipping": true, "is_gift_card": false, "is_upsell": false } ], "shipping_method": "Default", "shipping_code": "default", "total_incl_tax": "84.98", "total_excl_tax": "84.98", "total_discount": "0.00", "shipping_incl_tax": "4.99", "shipping_excl_tax": "4.99", "total_cost": "1.00", "total_tax": "0.00", "shipping_tax": "0.00", "display_taxes": "", "currency": "USD", "user": { "id": 123456, "email": "customer@gmail.com", "ip": "182.82.112.1", "first_name": "John", "last_name": "Doe", "phone_number": null, "accepts_marketing": true, "language": "en", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" }, "shipping_address": { "id": 7851, "first_name": "John", "last_name": "Doe", "line1": "2200 Western Pl", "line2": "", "line3": "", "line4": "Henderson", "postcode": "42304", "phone_number": "+18123158899", "notes": "", "state": "KY", "country": "US" }, "billing_address": { "id": 7814, "first_name": "John", "last_name": "Doe", "line1": "2200 Western Pl", "line2": "", "line3": "", "line4": "Henderson", "postcode": "423014", "phone_number": "+18123158899", "state": "KY", "country": "US" }, "date_placed": "2024-12-09T02:00:20.931086-05:00", "offer_discounts": [], "voucher_discounts": [], "attribution": { "agent": null, "funnel": null, "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "gclid": null, "metadata": {}, "affiliate": null, "subaffiliate1": null, "subaffiliate2": null, "subaffiliate3": null, "subaffiliate4": null, "subaffiliate5": null }, "metadata": {}, "transactions": [ { "id": 8127, "external_id": "109659", "type": "debit", "amount": "84.98", "currency": "USD", "parent_id": null, "status": "succeeded", "date_created": "2024-12-09T02:00:21.254596-05:00", "payment_method": "bankcard", "payment_details": { "gateway": { "id": 2, "type": "test", "name": "Test" }, "bankcard_first_six": "411111", "bankcard_last_four": "1111", "is_3ds": false, "optimized_3ds": false, "downgrade_3ds_retry": false, "sca_flow": null, "card_token": "01JEN3G3VP12C9KC75B" }, "response_code": 1000, "is_disputed": false, "is_external": false, "is_test": true, "is_initial_retry": false, "report_values": { "currency": "USD", "amount": "84.98" } } ], "order_status_url": "https://examplestore.com/accounts/order-status/109659/109659:VoL9PVdtkdDFq-Iog81_fQvBeiHjJdqgfhgDi6mvGg4/" }, "event_type": "checkout_completed", "timestamp": "2024-12-09T07:00:22.284164+00:00", "event_version": "2024-04-01" } ```
      # Checkout Links (https://developers.nextcommerce.com/docs/storefront/checkout-links) Checkout Links allow you add links from any website, email or web marketing channel directly to your store's checkout flow with items pre-loaded in their cart. ### Example Checkout Links [#example-checkout-links] #### As a one-time purchase [#as-a-one-time-purchase] With the link below, 2 products would be added to the cart with an applied voucher. ```bash title="One-Time Purchase" https://{domain}/checkout/add/?product=12:1&product=13:3&voucher=PROMO¤cy=usd ``` #### As a subscription [#as-a-subscription] With the following link, 1 product would be added to the cart as a subscription renewing every 3 months. ```bash title="Subscription" https://{domain}/checkout/add/?product=12:1:3:month¤cy=usd ``` ### Supported Parameters [#supported-parameters] Checkout link parameters can be broadly split into two groups, [Cart Parameters](#cart-parameters) controlling the products and discounts applied to the cart and [Attribution Parameters](#attribution-parameters) to marketing attribution reporting. #### Cart Parameters [#cart-parameters] | Parameter | Values | Description | | ---------- | ------------------------------- | -------------------------------------------------------------------------------------------------------- | | `product` | id:qty:interval\_count:interval | Pass product ID, quantity, interval count (number of) and interval (day, week, month) to add to the cart | | `voucher` | voucher code | Apply a voucher to add to the cart | | `currency` | currency code | Set the currency for the cart and product prices | | `replace` | true/false | replace existing cart, default is true | #### Attribution Parameters [#attribution-parameters] In addition to populating the cart, you can pass attribution parameters to attribute the order to your marketing channel. | Parameter | Description | | -------------------------------- | ----------------------------------------------------------- | | `utm_source` | The referrer: (e.g. google, newsletter) | | `utm_medium` | Marketing medium: (e.g. cpc, banner, email) | | `utm_campaign` | Product, promo code, or slogan (e.g. spring\_sale) | | `utm_term` | Identify the paid keywords | | `utm_content` | Use to differentiate ads | | `funnel` | Use to attribute funnels | | `gclid` | Adwords click ID | | `evclid` | Everflow Click ID | | `aff` | Main affiliate / network | | `sub1` | Sub affiliate 1 | | `sub2` | Sub affiliate 2 | | `sub3` | Sub affiliate 3 | | `sub4` | Sub affiliate 4 | | `sub5` | Sub affiliate 5 | | `attribution_metadata.KEY=VALUE` | Attribution Metadata key/value pair | | `clear_attribution=false` | Pass this to only update existing attribution for the cart. | > **Note (Last Click Attribution):** Attribution on carts and the subsequent orders uses "Last Click" attribution model meaning that each time new attribution is passed it will replace all existing attribution data for the cart. Passing `clear_attribution=false` as a querystring will keep existing attribution and update any new attribution passed.