Skip to content
Zid partners
Developer programsAn overview of every way to build on ZidApp PartnersPublish your app in the Zid App MarketTheme PartnersLaunch your theme in the Zid Theme Market
App MarketApps available to Zid merchantsTheme MarketThemes ready to install
Support
Partner help centreHow-to guides and troubleshootingDeveloper docsAPIs, SDKs and integration guidesBook a meetingA business or technical support call
What's new
BlogGuides and updates for developersChangelogThe latest partner platform updatesSuggest a featureThe feedback and ideas board
Partner enablement
Partner Ads SoonVisibility packages on Zid’s channelsCustom themesThemes built for one merchantPartner communityConnect with developers building on Zid
Zid library
Zid reportsEverything you need to know about the marketDeveloper guidesOfficial references to build on
العربيةLog inJoin as a partner

Partner programs

Developer programsApp PartnersTheme Partners

Browse the market

App MarketTheme Market

Support & enablement

Partner help centreDeveloper docsBook a meetingBlogChangelogSuggest a featurePartner Ads SoonCustom themesPartner communityZid reportsDeveloper guides

Language

العربية
Log inJoin as a partner
Home/Blog/Themes
Themes

Jinja in Zid themes: the templates, objects, filters and patterns you'll use every day

How Vitrin themes use Jinja: file layout, inheritance and includes, documented objects and helpers, product loops, translations, brands and the coupon-field setting.

Nov 8, 20266 min read

Where Jinja lives in a theme

Zid themes are powered by the Jinja templating engine. It has two forms: {{ ... }} outputs a value, and {% ... %} handles logic, loops and template structure. Zid supports Jinja's core filters and layers a small set of platform tags, filters and macros on top. This is the required folder structure:

├── locale/ar/LC_MESSAGES/messages.po
├── assets/
├── sections/
│   ├── slider.jinja
│   └── slider.schema.json
├── components/
│   └── pagination.jinja
├── templates/
│   ├── home.jinja
│   ├── cart.jinja
│   └── product.jinja
├── layout.jinja
├── layout.schema.json
├── footer.jinja
├── footer.schema.json
├── header.jinja
└── header.schema.json
  • Template and layout names and directories must be the ones the system recognizes (such as home.jinja, product.jinja, cart.jinja). Sections and components can be named as you like.
  • Each section .jinja needs a matching .schema.json in the same directory, and the layout, header and footer need layout.schema.json, header.schema.json and footer.schema.json in the theme root.
  • Assets live in assets/, are fingerprinted and served through Zid's CDN.

Inheritance, blocks and includes

Every template extends layout.jinja and fills its content block; the Template Library examples use a main_content block. Here's an excerpt of the product.jinja example from the docs:

{% extends "layout.jinja" %}

{% block main_content %}
  <h1>{{ product.name }}</h1>

  <div class="price">
    {% if product.selected_product.formatted_sale_price %}
      <span>{{ product.selected_product.formatted_sale_price }}</span>
      <del>{{ product.selected_product.formatted_price }}</del>
    {% else %}
      {{ product.selected_product.formatted_price }}
    {% endif %}
  </div>

  {{ product.description | safe }}

  {% include 'vitrin:products/variants_list.jinja' %}
  {% include 'vitrin:shared/bank_discounts.jinja' %}
{% endblock %}
  • include inserts another template at render time, such as components/pagination.jinja. To pass a small local context: {% with a=1 %}{% include 'x' %}{% endwith %}.
  • Platform-managed templates are included with the vitrin: prefix, such as vitrin:products/variants_list.jinja or vitrin:cart/products_list.jinja.
  • On the home page, {% template_components %} renders the sections merchants arrange in the Theme Editor.

Objects available in templates

Each page in the Template Library lists its variables. store and session are available on the documented templates, alongside a page-specific object:

  • product on product.jinja: name, slug, description (HTML) and selected_product, the current variant with formatted_price, formatted_sale_price, in_stock, sku and media. Also variants, options, rating, related_products and brand.
  • brand and products on brand.jinja (route /brands/{slug_brand}): the brand has id, name, slug, description and logo; the product list has results, count, page, pages_count and filters. The list of all brands is brands.jinja at /brands.
  • settings: a flattened version of your schema. A logo setting in the header group becomes settings.header_logo. Settings inside a list become a list of objects you loop over with safeget(settings, "images", []).
  • session: session.query_params.get('q') for query parameters, session.url.path for the current path, and session.url.include_query_params(page=2) to build a URL with extra query parameters.
  • store.settings: store-level settings, such as store.settings.checkout.is_coupon_form_enabled.
Paginated responses use results, count, page, page_size and pages_count, replacing old Twig keys such as data, total and last_page.

Platform helpers and filters

  • asset_url (filter): returns a versioned CDN URL for a file in assets/.
  • url_for: builds a route URL from an operation name and respects locale and routing rules, such as product_details, list_products, cart_page or faqs. It accepts query_params to append query strings. Brand routes aren't supported yet, so use /brands and /brands/{{ brand.slug }}.
  • image_url (macro): image_url(path, w=None, h=None, q=None, f=None). w and h are pixels, q is quality from 0 to 100, and f='auto' lets the browser pick the best format (AVIF or WebP).
  • Date and time: today(), now(), format_datetime and format_time, with timezone and locale already set.
  • Jinja core filters work as usual, including upper, length, safe (render raw HTML), default (or d), replace, tojson and urlencode.
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">

<a href="{{ url_for('product_details', slug=product.slug) }}">{{ product.name }}</a>
<a href="{{ url_for('faqs') }}">Help & FAQs</a>

<img src="{{ image_url(my_image, w=400, q=85, f='auto') }}" alt="">

{{ format_datetime(order.created_at, fmt="long") }}

Common patterns: loops, conditionals and null safety

The pattern you'll write most is a loop over a product list with filters and pagination. This is an excerpt of the brand.jinja example from the docs, and product listing pages follow the same shape:

{% extends "layout.jinja" %}

{% block main_content %}
  <h1>{{ brand.name }}</h1>

  {% if products.filters | length > 0 %}
    {% include 'vitrin:products/filters.jinja' %}
  {% endif %}

  <div class="products">
    {% for product in products.results %}
      {% include 'components/product-card.jinja' %}
    {% endfor %}
  </div>

  {% include 'components/pagination.jinja' %}
{% endblock %}
  • Jinja handles chained access to null values safely, but iterating over a null value or dividing by zero breaks rendering. Guard loops with {% if var is defined and var is iterable %}.
  • For defaults: {{ title or 'Untitled' }} or {{ title | default('Untitled') }}. default('x', true) also treats falsy values such as '' and 0 as missing.
  • To show the brand on a product page, check {% if product.brand %} first, then render product.brand.logo and product.brand.name.
  • For the current variant's availability use product.selected_product.in_stock, and product.selected_product.quantity for stock.

Translations with gettext

Templates contain literal English source strings, and locale/ar/LC_MESSAGES/messages.po maps each string (msgid) to its translation (msgstr). The core forms:

{{ _("Add to cart") }}

{{ _("Hello %(name)s", name=user.name) }}

{% trans count=n %}{{ count }} item{% pluralize %}{{ count }} items{% endtrans %}

{{ pgettext("button", "Open") }}
{{ pgettext("verb", "Open") }}
  • Keep strings literal and don't build them by concatenation, so extraction tools can find them.
  • Use %(name)s placeholders instead of .format().
  • {% trans %} with {% pluralize %} leaves plural forms to the locale's rules, which matters for Arabic and its six forms (msgstr[0] to msgstr[5]).
  • pgettext separates identical strings with different meanings. Keep every msgid unique in the file, and keep HTML tags outside translatable text.

Store settings that change what you render: the coupon field

Some merchant settings must be respected in your templates. Merchants control the coupon field under Settings, then Checkout Options, with Show coupon code field (enabled by default), and the theme reads store.settings.checkout.is_coupon_form_enabled:

{% set coupon_form_enabled = store.settings.checkout.is_coupon_form_enabled != false %}

{% if coupon_form_enabled %}
  <section class="cart-coupon" data-coupon-section>
    {# Coupon input, apply button, applied coupon details, and remove button #}
  </section>
{% endif %}
  • Use != false, not a plain truthy check, so the section stays visible when the value is null or missing.
  • Hide the complete section: input, apply button, applied coupon, remove button, and validation and error messages.
  • Don't hide it with CSS. The Jinja condition keeps the HTML from being generated at all.
  • Add null checks in JavaScript before touching coupon elements, and apply the condition on every cart surface.
  • Store settings may be cached for up to 10 minutes, so wait before judging a test result.

Key takeaways

  • Every template extends layout.jinja, and platform-managed templates are included with the vitrin: prefix.
  • Rely only on documented objects: store, session, settings and the page object such as product or products.
  • Use asset_url, image_url and url_for instead of hardcoded URLs, except for brand routes.
  • Wrap text in _() and {% trans %}, with a unique msgid for each string.
  • Guard against null values, and hide the coupon section only when the setting is explicitly false.

Sources

  1. Jinja Basics
  2. Vitrin's Jinja Extensions
  3. Templates: Overview
  4. Architecture
  5. home.jinja
  6. product.jinja
  7. brand.jinja
  8. Schema files
  9. localization (jinja v. twig)
  10. Twig to Jinja
  11. Performance
  12. Show or Hide the Coupon Code Field in a Zid Theme

Start building on Zid

Sign up in the partner portal, then build and test on a development store.

Create your account →

Contents

  1. Where Jinja lives in a theme
  2. Inheritance, blocks and includes
  3. Objects available in templates
  4. Platform helpers and filters
  5. Common patterns: loops, conditionals and null safety
  6. Translations with gettext
  7. Store settings that change what you render: the coupon field

Share

Start building

Related articles

→
Themes

Vitrin CLI in depth: from login to preview, push and troubleshooting

The core Vitrin CLI commands: install, login, creating a theme, previewing on a development store, install and activate, build and push, and fixing common errors.

5 min read
Themes

Build your first Theme Market theme: from structure to publishing

A practical walkthrough: how Zid themes are structured, local development and preview with Vitrin CLI, testing on a development store, submission and support.

5 min read
APIs & integration

Storefront events on Zid: track views, carts and purchases from your app scripts

Which storefront events Zid exposes, what each payload contains, how to add tracking scripts through Custom Snippets, and the zidTracking helpers for theme developers.

6 min read
Zid Partners

Partner programs

Developer programsApp PartnersTheme PartnersCreate your account

Resources

BlogZid reportsDeveloper docsPartner help centerChangelogRequest a feature

Enablement

Partner Ads Soon

Browse the market

App MarketTheme Market

Terms & policies

Partner terms and conditionsPrivacy policy

Get in touch

Book a business callBook a technical support callPartners community
Zid — Al-Qudrah Al-Taqniyah for Technology and Communicationالعربية
Al-Qudrah Al-Taqniyah for Technology and Communication CompanyCR No. 1010365366VAT No. 300827827900003