Skip to main content

Custom Twig Functions Reference API

SitePack registers a suite of custom Twig functions on top of the standard Twig environment. They compile down to native, sandboxed PHP calls, giving you high-performance building blocks for rendering layout chrome (headers, footers, navigation), commerce UI (cart, prices, stock), and platform integrations (icons, cookies, app blocks) — without writing any backend code yourself.

Using these built-in helpers ensures consistent e-commerce behavior, security sandboxing, and forward compatibility with future platform updates.


Complete Functions Reference Directory

theme_asset(path)

Generates the fully qualified, CDN-served URL of a static asset from inside your theme's own /assets directory.

  • Parameters:
    • path (string): The relative path to the asset from inside your root /assets directory.
  • Example Usage:
    <link rel="stylesheet" href="{{ theme_asset('css/theme.css') }}">
    <script src="{{ theme_asset('js/theme.js') }}" type="module"></script>
  • Tip: The asset_url filter (see the Filters Guide) is an identical alias — use whichever reads more naturally in context: theme_asset('css/theme.css') vs 'css/theme.css' | asset_url.

Reading your theme's settings

There is no function for this. Every setting you declare in theme.json is emitted as a CSS custom property, so your stylesheet consumes it directly:

header { background: var(--header-background-color); }

See the theme.json Reference. Content-shaped fields on a custom template are read from the page object instead: page['hero-title'].

sitepack_title()

Outputs the correct <title> tag content for the current route (product name, category name, blog post title, static page title, etc., falling back to the store name).

<title>{{ sitepack_title() }}</title>

sitepack_head()

Renders the required SitePack <head> payload (asset preload/link tags for active editor elements and the compiled theme, plus other platform requirements). Always call it inside <head>, right after your <title> tag.

<head>
<title>{{ sitepack_title() }}</title>
<meta charset="UTF-8">
{{ sitepack_head() }}
<link rel="stylesheet" href="{{ 'css/theme.css' | asset_url }}">
</head>

sitepack_navigation()

Renders the merchant's configured main navigation menu (including nested dropdown items) as semantic markup.

<nav>
<ul>
{{ sitepack_navigation() }}
</ul>
</nav>

sitepack_breadcrumbs()

Renders the hierarchical breadcrumb trail for the active route (e.g., Home > Men's > Jackets). Typically placed once in layouts/base.twig, right after the header include.

{{ sitepack_breadcrumbs() }}

sitepack_translations()

Renders a ready-made language switcher: a <nav><ul><li> list of direct links to the current page in every available store language, each <a> carrying the correct hreflang/lang attributes and the locale prefix (NL, DE, EN, …) as its label. The list item for the active language gets class="active". If the store has fewer than two languages it renders nothing, so it is safe to call unconditionally.

  • Parameters:
    • attributes (hash, optional): Pass { class: '...' } to override the default <nav> class (sitepack-translations).
  • Example Usage:
    {# Default markup #}
    {{ sitepack_translations() }}

    {# With a custom wrapper class #}
    {{ sitepack_translations({ class: 'lang-switcher' }) }}
  • Example Output:
    <nav class="sitepack-translations" aria-label="Languages">
    <ul>
    <li class="active"><a href="https://store.example/nl/test" hreflang="nl_NL" lang="nl_NL">NL</a></li>
    <li><a href="https://store.example/de/test" hreflang="de_DE" lang="de_DE">DE</a></li>
    </ul>
    </nav>

sitepack_translations_list()

Returns the same language-alternate data as a plain associative array instead of markup, so you can build a fully custom switcher (a <select>, a flag dropdown, etc.). Each key is the URL locale prefix (nl, de, en, …) and each value is the absolute URL of the current page in that language. Returns an empty array when the store has fewer than two languages.

  • Returns: array<string, string> — e.g. { nl: 'https://store.example/nl/test', de: 'https://store.example/de/test' }.
  • Example Usage:
    {% set languages = sitepack_translations_list() %}
    {% if languages|length > 1 %}
    <ul class="lang-switcher">
    {% for prefix, url in languages %}
    <li class="{{ prefix == site.locale[:2] ? 'active' : '' }}">
    <a href="{{ url }}" hreflang="{{ prefix }}">{{ prefix|upper }}</a>
    </li>
    {% endfor %}
    </ul>
    {% endif %}
  • Tip: Both switcher helpers link to the equivalent page in each language (the hreflang alternates SitePack resolves per route), not just the language homepage — so a visitor stays on the same product, category or article when they switch. See the Translations Guide for the full internationalization workflow.

sitepack_slug(path, absolute)

Resolves a link to another page of the store, addressed by that page's default slug — the one the merchant sees in the admin — into the address the language currently being read is actually served under. Use it for every hard-coded link in your theme (/pricing, /contact, a CTA in your footer): a plain href="/pricing" throws a visitor reading /de/ straight out of their language, and a theme cannot know translated slugs.

  • Parameters:

    • path (string): The page's default path, with or without slashes. Nested pages take their full path ('services/hosting'); every segment is localized.
    • absolute (bool, optional, default false): Return a full URL instead of a root-relative path. The host is the one the request is being served from, so a link rendered on staging keeps you on staging.
  • Returns: string — a root-relative path such as /de/preise, or an absolute URL when absolute is true.

  • Example Usage:

    <a href="{{ sitepack_slug('pricing') }}">{{ 'nav.pricing'|trans }}</a>
    <a href="{{ sitepack_slug('services/hosting') }}">{{ 'nav.hosting'|trans }}</a>

    {# Back to the homepage of the language being read #}
    <a href="{{ sitepack_slug('/') }}">{{ site.name }}</a>
  • What it returns, per language being rendered:

    Rendered undersitepack_slug('pricing')
    the store's main language/pricing
    /de/, page translated as preise/de/preise
    /fr/, page not translated/fr/pricing
    a store that sells no translations/pricing
  • Tip: It never invents an address. A path that is not a page, a page without a translated slug, or a store without translations all fall back to the language prefix plus the path you passed — which is exactly what the router serves — so it is always safe to use, even on a single-language store. See the Translations Guide.

sitepack_product_slug(item, absolute) / sitepack_category_slug(item, absolute) / sitepack_blog_slug(item, absolute)

The same idea as sitepack_slug(), for the three kinds of content served from a fixed route: a product (/products/…), a category (/categories/…) and a blog post (/blog/…). Use them for every link to one of those, listings included.

Pass the item itself — product, category, article — not one of its fields. A listing is assembled in the store's main language, so the url on each item is that language's address; the item also carries the address it has in every other language, and these helpers read the right one off it.

  • Parameters:

    • item (object|array|string): The product, category or article. A slug ('red-shoe') or path ('/products/red-shoe') is also accepted for links you write by hand; an absolute URL is reduced to its path. Anything after the slug — a category page number, a filter segment, a query string, a fragment — is kept exactly as it is.
    • absolute (bool, optional, default false): Return a full URL instead of a root-relative path, on the host the request is being served from.
  • Returns: string — a root-relative path such as /de/products/roter-schuh, or an absolute URL when absolute is true.

  • Example Usage:

    <a href="{{ sitepack_product_slug(product) }}">{{ product.name }}</a>
    <a href="{{ sitepack_category_slug(category) }}">{{ category.name }}</a>
    <a href="{{ sitepack_blog_slug(article) }}">{{ article.title }}</a>

    {# The overview of the language being read: /products, /de/products #}
    <a href="{{ sitepack_product_slug('') }}">{{ 'nav.shop'|trans }}</a>
  • What they return, per language being rendered:

    Rendered undersitepack_product_slug(product)
    the store's main language/products/red-shoe
    /de/, product translated as roter-schuh/de/products/roter-schuh
    /fr/, product not translated/fr/products/red-shoe
    a store that sells no translations/products/red-shoe
The route segment is never translated

/de/produkte/… is served by nothing. Only the item's own slug changes per language; products, categories and blog stay as they are in every language, which is why you should never assemble one of these addresses by hand.

Building a card from a hash

If you pass a hash into a snippet instead of the item itself, carry translations across or the card loses the translated address:

{% include 'snippets/category-card.twig' with { category: {
url: sub_category.slug,
translations: sub_category.translations|default([]),
categoryName: sub_category.name
} } %}
  • Tip: Like sitepack_slug(), they never invent an address. An item with no translations (a single-language store) keeps its own path; a bare slug keeps the slug behind the right prefix — both are addresses the router serves. Building the address yourself is what breaks: "/de/products/" ~ product.slug 404s the moment the merchant translates that slug, and product.url on its own silently drops the visitor's language.

sitepack_content()

Renders the main routed page content. This is used internally by the platform to inject the resolved {% block content %} — as a theme developer you generally won't call this yourself inside page templates (you use {% block content %}...{% endblock %} instead), but it's used by sitepack_content_legal()-style helpers and internal layout composition.

sitepack_elements(elements)

Renders an array of merchant-authored content elements (rich text blocks, image blocks, banners, etc. added via the page/category editor) in order.

  • Parameters:
    • elements (array): The elements array, e.g. category.elementsAbove or category.elementsBelow.
  • Example Usage:
    {% if category.elementsAbove %}
    <section class="category-elements-above">
    {{ sitepack_elements(category.elementsAbove) }}
    </section>
    {% endif %}

Renders the active legal document's rich-text body (Terms & Conditions, Privacy Policy, Return Policy, etc.) on templates/legal.twig.

{% block content %}
{{ sitepack_content_legal() }}
{% endblock %}

Renders the merchant's configured footer link columns.

<footer>
{{ sitepack_footer() }}
</footer>

Renders a simple inline list of links to the store's published legal pages (Privacy Policy, Terms, etc.) — handy for a footer legal bar.

<div class="footer-legal-links">
{{ sitepack_legal_links() }}
</div>

Renders the copyright line (e.g., © 2026 Store Name. All rights reserved.), with optional extras.

  • Parameters:
    • showPolicies (boolean, optional, default false): Also render legal policy links.
    • showSiteMapLink (boolean, optional, default false): Also render a link to the sitemap page.
    • showShortDescription (boolean, optional, default false): Also render the store's short description text.
  • Example Usage:
    <footer>
    {{ sitepack_footer() }}
    {{ sitepack_copyright(true, true, false) }}
    </footer>

sitepack_icon(name)

Renders an inline SVG icon from SitePack's built-in icon library. See the Icons Reference for the full catalog of available names.

{{ sitepack_icon('angle-right-solid') }}

Renders the full interactive live-search UI (search input, predictive results dropdown, and modal wrapper).

{{ sitepack_live_search() }}

sitepack_live_search_icon()

Renders just the search trigger icon (used to open the live search modal via JavaScript), for placing inside a compact header nav item.

<li class="nav-icon nav-search-icon" id="nav-search">
{{ sitepack_live_search_icon() }}
</li>

sitepack_shopping_cart_icon(icon, showCount)

Renders the shopping cart icon with a live item-count badge.

  • Parameters:
    • icon (string, optional): Icon name to render (see Icons Reference). Default: 'shopping-bag-solid'.
    • showCount (boolean, optional): Whether to show the live quantity badge. Default: true.
  • Example Usage:
    <li class="nav-icon nav-shopping-cart" id="nav-cart">
    {{ sitepack_shopping_cart_icon() }}
    </li>

sitepack_category_filters()

Renders the interactive faceted-filter sidebar UI (price ranges, brand checkboxes, attribute filters) for the current category listing.

<div id="sidebar-category-filters">
{{ sitepack_category_filters() }}
</div>

sitepack_cart()

Renders the full interactive shopping cart contents — line items, quantity controls, totals, and checkout button — on templates/cart.twig.

{% if cart.totalProducts > 0 %}
<div class="cart">
{{ sitepack_cart() }}
</div>
{% endif %}

sitepack_cookies()

Renders the merchant's configured GDPR/ePrivacy cookie consent banner. Place this near the end of layouts/base.twig, before your closing scripts.

{{ sitepack_cookies() }}

sitepack_scripts()

Reserves the position, right before </body>, where platform-level scripts belong. Always include this call in layouts/base.twig for forward compatibility, even though it may not always render visible output today.

{{ sitepack_scripts() }}
<script src="{{ 'js/theme.js' | asset_url }}" type="module"></script>
</body>

app_block 'name'

This is a Twig tag, not a function — always use the {% app_block 'name' %} block syntax. It renders any content that installed third-party apps have registered for the given hook name (see the Template Hooks Guide for the full list of standard hook names). If no app targets that hook, it silently renders nothing.

<div class="custom-promotional-strip">
{% app_block 'header_top' %}
</div>

sitepack_price(priceCents, currency)

Converts an integer cents value into a formatted, human-readable currency string.

  • Parameters:
    • priceCents (integer): Price value represented in cents (e.g., 2450).
    • currency (string, optional): Currency symbol. Default: '€'.
  • Example Output: € 24,50
  • Example Usage:
    <span class="price-tag">{{ sitepack_price(product.priceCents) }}</span>

sitepack_products(limit, type, category)

Returns a list of products as data (an array of product objects), so you can render a product row anywhere — most usefully in a custom template you assign as your homepage. Unlike templates/index.twig, a custom template does not receive the frontpage collection, so this function is the supported way to pull products into it. Loop over the result and hand each item to your product card snippet exactly as you would with collections.frontpage.products.

  • Parameters:
    • limit (integer, optional): How many products to return (1-48). Default: 4.
    • type (string, optional): Which selection to return — 'newest', 'popular' or 'sale'. Default: 'newest'.
    • category (string, optional): A category slug. When given, the selection is scoped to that category; when omitted (or empty), it spans the whole shop.
  • Returns: array of product objects — the same shape used everywhere else in the theme, so product.name, product.priceCents, product.pricePromoCents, product.url, product.first_image.thumbnail, product.brand, etc. are all available. Returns an empty array when the store has no store module or no matching products.
  • Example Usage:
    {# In a custom "home" template: a "New in" product row #}
    {% set products = sitepack_products(8) %}
    {% if products|length > 0 %}
    <div class="product-grid">
    {% for product in products %}
    {% include 'snippets/product-card.twig' with { product: product } %}
    {% endfor %}
    </div>
    {% endif %}

    {# The most popular products in one category #}
    {% for product in sitepack_products(4, 'popular', 'jurken') %}
    {% include 'snippets/product-card.twig' with { product: product } %}
    {% endfor %}

    {# A sale / discount row across the whole shop #}
    {% for product in sitepack_products(8, 'sale') %}
    {% include 'snippets/product-card.twig' with { product: product } %}
    {% endfor %}
  • Tip: templates/index.twig receives collections.frontpage.products for the default homepage grid (see Homepage Collections). Use sitepack_products() when you build your own home template, or anywhere else you want a product row (a category teaser on a landing page, related picks in the footer, and so on).

Site content managed in the admin

The functions below render (or return) content a merchant manages centrally in the admin — the newsletter, the USPs, the topbar and the agenda. Prefer them over hardcoding this content in your templates or translation files: the merchant can then edit it, it is the same everywhere it appears, and it stays in sync (a free-shipping USP tracks the real shipping setting, an event drops off the agenda once it is in the past). See the overview for which content this replaces.

sitepack_newsletter_form()

Renders the complete newsletter subscription form as HTML: the intro text, the email field, the submit button (all from the merchant's newsletter settings), plus a CSRF token and a honeypot. The form works without JavaScript (it posts to /newsletter/subscribe) and is progressively enhanced by the SDK. It renders nothing when the merchant has the newsletter turned off, so it is safe to call unconditionally.

  • Parameters: none.
  • Returns: an HTML string (empty when the newsletter is disabled).
  • Example Usage:
    <section class="newsletter">
    {{ sitepack_newsletter_form() }}
    </section>
  • Tip: if you want to build your own markup instead, the raw texts are on the newsletter object — newsletter.enabled, newsletter.texts.intro, newsletter.texts.button, newsletter.texts.confirmation (see Twig Objects).

sitepack_usps(placement)

Returns the site's reusable USPs as data (an array), so you can render them however your design needs. USPs are global: the merchant maintains one ordered list and the same set is available everywhere you call this. A USP entered once is reused across the site without being re-typed. Free-text values are already resolved for the page language and the free-shipping threshold is interpolated.

  • Parameters:
    • placement (string, optional): kept for backwards compatibility only. USPs no longer carry per-placement flags, so this argument is ignored — every visible USP is returned regardless of what you pass. Prefer calling sitepack_usps() with no argument and slicing the result yourself (| slice(0, 3)) if a spot should show fewer.
  • Returns: array of USP objects: usp.uuid (string), usp.title (string), usp.text (string, may be empty), usp.icon (an icon name for sitepack_icon(), may be empty). Order follows the merchant's sort order in the admin. Returns an empty array when the site has no visible USPs — render nothing in that case so you keep no empty block.
  • Example Usage:
    {% set usps = sitepack_usps() %}
    {% if usps|length >= 1 %}
    <div class="usp-strip">
    {% for usp in usps %}
    <div class="usp">
    {% if usp.icon %}<span class="usp__icon">{{ sitepack_icon(usp.icon) }}</span>{% endif %}
    <span class="usp__title">{{ usp.title }}</span>
    {% if usp.text %}<span class="usp__text">{{ usp.text }}</span>{% endif %}
    </div>
    {% endfor %}
    </div>
    {% endif %}

sitepack_topbar()

Renders the merchant-managed topbar as HTML. There is no free-form editor: the merchant turns the bar on and picks one of two fixed layouts in the admin (Thema → Topbalk), and this function prints the matching markup. Renders nothing when the topbar is turned off — or when the chosen layout has no content yet — so it leaves no empty strip and is safe to call unconditionally.

The two layouts are:

  • Standard — the first 1–3 of the site's USPs (title + icon, from the USP screen) on the left, and a chosen location's email and phone as mailto: / tel: links on the right. Both are re-resolved live, so editing the USP screen or the location updates the bar.

  • Text — a single centered rich-text line, sanitized to the safe-HTML allowlist on save (emojis and links allowed).

  • Parameters: none.

  • Returns: an HTML string (empty when the topbar is off or empty).

  • Markup you style: you do not control the inner markup, so target these classes in your stylesheet and provide styles for both layouts (the merchant can switch at any time):

    <!-- Standard -->
    <div class="topbar-standard">
    <div class="topbar-standard__usps">
    <span class="topbar-usp"><span class="topbar-usp__icon"></span><span class="topbar-usp__label"></span></span>
    </div>
    <div class="topbar-standard__contact">
    <a class="topbar-contact__item topbar-contact__email" href="mailto:…"></a>
    <a class="topbar-contact__item topbar-contact__phone" href="tel:…"></a>
    </div>
    </div>

    <!-- Text -->
    <div class="topbar-text"></div>
  • Example Usage:

    {# In snippets/header.twig — render the core topbar, print nothing when it is off #}
    {% set core_topbar = sitepack_topbar() %}
    {% if core_topbar %}
    <div class="topbar"><div class="topbar__inner">{{ core_topbar }}</div></div>
    {% endif %}
  • Colours: the bar uses the theme's topbar colour (and its text colour). The merchant edits those straight from the topbar screen; they map onto your theme's topbar colour setting, so keep reading that setting for the strip's background rather than hardcoding a colour.

  • Mobile note: the standard layout is a two-column strip (USPs / contact) — collapse it as you see fit under 768px (a common choice is to keep the contact links and hide the USP strip, or wrap to two short rows). The text layout is one line; let it wrap or truncate.

sitepack_events(limit, scope)

Returns the site's events (the agenda) as data (an array). By default only upcoming events are returned; pass a scope to fetch past or all events. Each event's date is already formatted in the page language (the sandbox only allows a handful of filters, so formatting happens server-side) and each carries ready-to-print schema.org Event JSON-LD.

  • Parameters:
    • limit (integer, optional): how many events to return. Default: 5.
    • scope (string, optional): which events to return — 'upcoming' (default, soonest first), 'past' (most recent first) or 'all' (newest first). Any other value falls back to 'upcoming'.
  • Returns: array of event objects: event.title, event.description, event.date_formatted (localised, e.g. "di 15 sep 2026"), event.start_date / event.end_date (YYYY-MM-DD), event.start_time / event.end_time (HH:MM or null), event.location (resolved place, may be empty), event.link, event.image, and event.json_ld (a schema.org Event object). Returns an empty array when there are no matching events — render nothing in that case.
  • Example Usage:
    {# upcoming events (default) #}
    {% set events = sitepack_events(6) %}

    {# past events, most recent first #}
    {% set past_events = sitepack_events(6, 'past') %}
    {% if events|length >= 1 %}
    <div class="agenda">
    {% for event in events %}
    <div class="agenda__row">
    <span class="agenda__date">{{ event.date_formatted }}</span>
    <span class="agenda__name">{{ event.title }}</span>
    {% if event.location %}<span class="agenda__location">{{ event.location }}</span>{% endif %}
    </div>
    {{ sitepack_json_ld(event.json_ld) }}
    {% endfor %}
    </div>
    {% endif %}
  • Tip: always emit event.json_ld through sitepack_json_ld() (see below), never with json_encode|raw — it renders a safe <script type="application/ld+json"> block and gives you valid schema.org Event structured data for free, good for search visibility of markets, fairs and in-store days.

sitepack_json_ld(data)

Renders a schema.org JSON-LD object as a safe <script type="application/ld+json"> block. Use it for any structured-data object the platform hands you — most commonly event.json_ld from sitepack_events().

  • Parameters:
    • data (object): a JSON-LD object (e.g. event.json_ld). An empty or missing value renders nothing.
  • Returns: an HTML string (a complete <script> tag, or empty).
  • Why this and not json_encode|raw: the JSON is encoded server-side with HTML-hex escaping, so a value such as an event title of </script>… can never break out of the script element. The generic raw filter is not available in the theme sandbox for exactly this reason.
  • Example Usage:
    {% for event in sitepack_events(6) %}
    <div class="agenda__row">{{ event.title }}</div>
    {{ sitepack_json_ld(event.json_ld) }}
    {% endfor %}
  • Note: you do not emit the page-level SEO JSON-LD yourself — sitepack_head() already outputs it in the <head>.

calculate_text_color(backgroundColor)

Analyzes the luminosity of a hex background color and returns whichever of #000000 (black) or #ffffff (white) gives better text contrast.

  • Parameters:
    • backgroundColor (string): A hex color code (e.g., #ffffff).
  • Example Usage:
    <header style="background-color: {{ settings.header_color }}; color: {{ calculate_text_color(settings.header_color) }}">

calculate_brightness(hex, steps)

Lightens or darkens a hex color by a number of steps — useful for generating hover/active states from a single merchant-picked color.

  • Parameters:
    • hex (string): Hex color code to adjust.
    • steps (integer): Positive values lighten, negative values darken.
  • Example Usage:
    <style>
    .btn-primary { background-color: {{ settings.button_color }}; }
    .btn-primary:hover { background-color: {{ calculate_brightness(settings.button_color, -20) }}; }
    </style>

The Master Layout Integration Blueprint

Here is the production layouts/base.twig from the Official Theme Skeleton, showing how these functions compose together in practice:

<!DOCTYPE html>
<html lang="{{ site.locale }}">
<head>
<title>{{ sitepack_title() }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{ sitepack_head() }}
<link rel="stylesheet" href="{{ 'css/theme.css' | asset_url }}">
{% app_block 'head_after' %}
</head>
<body class="{{ body_class }}">
{% app_block 'body_start' %}
{% include 'snippets/header.twig' %}

<main>
{% block content %}{% endblock %}
</main>

{{ sitepack_breadcrumbs() }}

{% include 'snippets/footer.twig' %}

{{ sitepack_cookies() }}
{{ sitepack_scripts() }}
<script src="{{ 'js/theme.js' | asset_url }}" type="module"></script>
{% app_block 'body_end' %}
</body>
</html>

Exploring Further:

Now that you have mastered custom rendering functions, discover the custom data string filters available for theme templates inside the Custom Theme Filters Guide!