Twig templating code is how are all commercehq themes are built. Twig allows us to load dynamic content on any store page. Even the visual builder is working with twig under the hood. With twig a developer can build a fully custom store with almost no limitations.


Splitting products and search results across multiple pages is a necessary part of theme design as you are limited to 50 results per page in any for loop.

{% set paginator ={"where": ["like", "products.title", "shirt"], "order": "products.title DESC"}), 50) %}

this way you will be able to access the next properties and methods:

paginator.items - products available on current page
paginator.totalCount - number of total items by the selected query (in this example: title of the product should match "shirt")
paginator.pageCount - number of total pages
paginator.pageSize - number of items per page
paginator.links - array of links for pagination navigation (self - current, first, prev, next, last)


CMS is very powerful instrument for store customization, user can pull whatever he wants from CMS to twig editor.

CMS consists of Content Groups and Items.

Each Content Group can include unlimited number of Items, and one Item can belong to only one Content Group.

Example of Content Group - Blog posts. Each post is a new item. You can also create another Content Group, let's name it Editors.

Now you're able to reference some post to specific author.

You can also reference multiple items to one post, e.g. websites.

{% set item = load.cms_item("blog", "test-post") %}

Blog post name: {{ }}

Author name: {{ item.settings['author'].name }}
{% for website in item.settings['websites'] %}

website name: {{ }}

{% endfor %}

Templates logic

So well, basically each theme should include 2 mandatory CMS-related files: cms_index.twig and cms_item.twig

If you want to customize template of specific group, you have to create the "cms" folder inside of your theme. And create folder with handle name of your content group inside of that "cms" folder.

  • create _index.twig if you want to customize index page of specific content group

  • create _item.twig

  • create item template .twig inside of content group folder.

Flow how templates logic works (example content group name: blog, example item name: post1):

For content groups index pages
step 1: Trying to render template from /cms/blog/_index.twig, if it doesn't exists - step 2

step 2: Renders template from /cms_index.twig

For content groups items
step 1: Trying to render template from /cms/blog/post1.twig, if it doesn't exists - step 2

step 2: Trying to render template from /cms/blog/_item.twig, if it doesn't exists - step 3

step 3: Renders template from /cms_item.twig

Global Objects

We have global object named "obj", which you can access from everywhere in your store. Here's how you can benefit from that:

  • obj.cartSize - returns cart size
  • obj.currency - returns currency defined as default in store settings
  • obj.canonical_url - The canonical_url object returns the canonical URL for the current page. The canonical URL is the page's "default" URL with any URL parameters removed.

For products and variants, the canonical URL is the default product page with no collection or variant selected.
  • obj.collections - The collections object returns all the collections in your store
  • obj.request - The request object returns information about the domain used to access the store. Use to check which domain a customer is visiting from. For example, if you have multiple domains, you can show a different greeting based on the request.
  1. obj.request.method - returns HTTP method
  2. - returns HTTP host
  3. obj.request.userAgent - returns userAgent
  4. obj.request.referrer - returns HTTP referrer
  5. obj.request.isAjax - returns (bool) value, if request is ajax request or not
{%if == ''%}
  Welcome USA!
{%elsif == ''%}
 Welcome Canada!
  • - The store object contains information about your store.
    {{ }} - returns store title
  • obj.generalSettings - The settings object lets you access the settings of a store
  1. obj.generalSettings.timezone - returns store's timezone
  2. obj.generalSettings.default_currency - returns store's default currency
  3. obj.generalSettings.analytics_code - returns analytics code from settings
  • obj.checkoutSettings - returns checkoutSettings instance
  • obj.theme - The theme object returns the store's published theme.
    {{ obj.theme.title }} - returns active theme title


Here's some important notes regarding to working with layouts

{{ void(this.beginPage()) }}before declaring doctype or opening Yes
{{ void(this.beginBody()) }}after Yes
{{ void(this.endBody()) }}before Yes
{{ void(this.endPage()) }}after tagYes
app.controller.getCartSize()Returns Cart sizeNo
site_path(app.homeUrl)Returns home urlNo
url.base(true)Returns root url (use only for assets!)No
site_path("/cart")Returns proper path to cart (also might be used with another locations)No
app.controller.getCollections()Returns list of collectionsNo
system.getApp('appName')Calls internal appNo
system.getBaseDir()Returns proper root path to your theme folderNo


Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (|) and may have optional arguments in parentheses. Multiple filters can be chained. The output of one filter is applied to the next.

{{ name|striptags|title }}
{{ list|join(', ') }}
{% filter upper %}
    This text becomes uppercase
{% endfilter %}

Go to the filters page to learn more about built-in filters.


Handles are used to access the attributes of objects. Most objects (products, collections, CMS) have handles.

Basic Handles

product handles - allows you to get product instance by it's url

{{ load.product("super-t-shirt").title }}

going to return title of product which is available in the store under /products/super-t-shirt

collection handles - allows you to get collection instance by it's url

{{ load.collection("nike").title }}

going to return title of collection which has url = "nike"

CMS Handles

{{ load.cms_item("blog", "test-post").name }}

will pull item with handle = "test-post" from content group with handle = "blog"

Smart handles


{% for product in load.get( loadQuery.products({ 
    where: [
        ['>=', 'price', 10],
        ['like', 'title', 'test']
    offset: 5,
    order: 'price ASC',
    limit: 100
    }) ) %}
        {{ product.price }} - {{ product.title }}<br>
{% endfor %}

going to find products with title matching "test" and price more than 10, sort them by price ascending, skip first 5 results and return next 100 results (e.g. 0-4 will be skipped, and 5-104 will be returned)


{% for collection in load.get( loadQuery.collections({ 
    where: [
        ['like', 'title', 'test']
    offset: 5,
    order: 'title ASC',
    limit: 100
    }) ) %}
        {{ collection.price }} - {{ collection.title }}<br>
{% endfor %}

going to find collections with title matching "test", sort them by title ascending, skip first 5 results and return next 100 results (e.g. 0-4 will be skipped, and 5-104 will be returned)


{% for item in load.get( loadQuery.cms_items("blog", {
offset: 1,
order: 'item_order ASC',
limit: 100
}) ) %}
    {{ }} - {{ }}<br>
{% endfor %}

going to skip first item, find 100 items from content group "blog" and sort them by the same order as in the admin panel

Image resizing

his method is supported for collection/product image and files (only images)
here's couple examples how to do so

{{ collection.getImageSize("large") }} for collections
{{ product.mainImage.getImageSize("thumbnail") }}