> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinmarkt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Nunjucks Design

> Design a Join Markt storefront with Nunjucks templates, layouts, assets, and settings.json.

# Nunjucks Design

**Nunjucks only** — `.njk` + `settings.json` + `assets/`.

For React / Visual Builder, open [Vite Design](/storefront/vite-design).

Join Markt does **not** parse Liquid.

***

## What you design

| Layer             | Where                                               |
| ----------------- | --------------------------------------------------- |
| Shell layout      | `layouts/base.njk`                                  |
| Pages             | `templates/*.njk` — filename = router name          |
| Reusable chrome   | `snippets/`, `macros/`, `components/`               |
| CSS / JS / images | `assets/` via the `assetUrl` filter                 |
| Theme settings    | `settings.json`                                     |
| Data              | Server context (`store`, `catalogItems`, `product`) |

Import with **Code / ZIP**, not Visual Builder.

***

## Folder shape

```text theme={null}
my-theme/
  layouts/base.njk
  templates/
    shop.njk
    products.njk
    product.njk
    privacy-policy.njk
    terms.njk
    refund-policy.njk
  snippets/
  components/
  macros/
  assets/style.css
  settings.json
```

***

## Routes (Nunjucks URLs)

| URL               | Template file                  |
| ----------------- | ------------------------------ |
| `/`               | `templates/shop.njk` (or home) |
| `/products`       | `templates/products.njk`       |
| `/product/:slug`  | `templates/product.njk`        |
| `/privacy-policy` | `templates/privacy-policy.njk` |
| `/terms`          | `templates/terms.njk`          |
| `/refund-policy`  | `templates/refund-policy.njk`  |

Do **not** use Vite paths (`/privacy`, `/refunds`) or wrong names (`privacy.njk`, `refunds.njk`).

***

## Design rules

1. Extend `layouts/base.njk` and fill the content block
2. Build links with the `shopUrl` filter
3. Load CSS/JS with the `assetUrl` filter
4. Card price = `minPrice` / `maxPrice` + `formatPrice`
5. Homepage grid = `catalogItems` (groups first)
6. On-hold when `product.visibility` equals `on-hold`
7. Policy body from `store.policies.privacy` / `terms` / `refund`
8. `autoescape` is off — read [Nunjucks Pitfalls](/storefront/nunjucks-pitfalls) before shipping
9. Cart: lean API lines with `productId`, `variantId`, `quantity`

***

## Policy page example

```njk theme={null}
{# templates/privacy-policy.njk #}
{% extends "layouts/base.njk" %}
{% block content %}
  <h1>Privacy Policy</h1>
  {% if store.policies.privacy %}
    <div class="policy-body">{{ store.policies.privacy | safe }}</div>
  {% else %}
    <p>No privacy policy has been added yet.</p>
  {% endif %}
{% endblock %}
```

Footer links (inside templates):

```njk theme={null}
<a href="{{ '/privacy-policy' | shopUrl }}">Privacy</a>
<a href="{{ '/terms' | shopUrl }}">Terms</a>
<a href="{{ '/refund-policy' | shopUrl }}">Refunds</a>
```

Full policy map: [Policy Pages](/storefront/policy-pages).

***

## Homepage grid example

```njk theme={null}
{% for item in catalogItems.slice(0, 12) %}
  {% if item.kind == 'group' %}
    <a href="{{ ('/group/' ~ item.group.id) | shopUrl }}">{{ item.group.name }}</a>
  {% else %}
    {% set product = item.product %}
    <a href="{{ ('/product/' ~ product.slug) | shopUrl }}">
      <img src="{{ product.image | imageUrl }}" alt="{{ product.name }}" />
      <h3>{{ product.name }}</h3>
      <span>{{ product.minPrice | formatPrice(currency) }}</span>
    </a>
  {% endif %}
{% endfor %}
```

Tags: `markt_component`, `markt_snippet` — [Shortcodes & Tags](/storefront/shortcodes).

***

## Next

* [Nunjucks Theme API](/storefront/nunjucks)
* [Shortcodes & Tags](/storefront/shortcodes)
* [Nunjucks Pitfalls](/storefront/nunjucks-pitfalls)
* [Template Structure](/storefront/template-structure)
