> ## 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.

# Build a Liquid theme

> Build a Join Markt theme with Shopify-style Liquid sections.

A Liquid theme writes each section as an `index.liquid` file, in Shopify-style syntax. No JavaScript needed.

> [!NOTE]
>
> Real example: **Noir** in `templates/themes/noir/`.

## The folder

```text theme={null}
my-theme/
├── theme.json              # "runtime": "liquid"
├── index.html
├── config/  groups/  templates/  public/
├── snippets/
│   └── icon.liquid         # reused with {% render 'icon' %}
└── src/
    └── sections/
        └── cta-banner/
            ├── index.liquid  # the design
            ├── index.ts      # optional script
            └── schema.json
```

## Steps

**1. Write theme.json**

```json theme.json theme={null}
{
  "engine": "join-markt/v2",
  "id": "my-theme",
  "name": "My Theme",
  "version": "1.0.0",
  "runtime": "liquid"
}
```

**2. Write index.html**

The same file as in React. It must load `/src/platform/entry.tsx`.

**3. Add a section**

`schema.json` is the same as in React. The design:

```liquid src/sections/cta-banner/index.liquid theme={null}
<div class="cta">
  {%- if settings.heading != blank -%}
    <h2>{{ settings.heading }}</h2>
  {%- endif -%}
  {%- if settings.text != blank -%}
    <p>{{ settings.text }}</p>
  {%- endif -%}
  {%- if settings.button_label != blank and settings.button_url != blank -%}
    <a href="{{ settings.button_url | url }}" class="btn">{{ settings.button_label }}</a>
  {%- endif -%}
</div>
```

This is a cut-down copy of Noir's real CTA banner.

**4. Zip, upload, publish**

See [Upload rules](https://docs.joinmarkt.com/themes/upload-rules.md).

## Objects

| Object | What it is |
| - | - |
| `settings` | this section's settings |
| `blocks` | this section's blocks, in order |
| `theme` | the theme settings (colors, fonts) |
| `products` | the store's public products |
| `cart` | the cart |
| `shop` | store name, logo, links |

## Filters

| Filter | What it does | Example |
| - | - | - |
| `money` | price in the store currency | `{{ product.price \| money }}` |
| `money_without_currency` | price, 2 decimals, no sign | `19.99` |
| `url` | store link that works live and in the preview | `{{ '/products' \| url }}` |
| `image_url` | full link to an image | `{{ settings.image \| image_url }}` |
| `asset_url` | link to a file in `public/` | `{{ 'logo.png' \| asset_url }}` |
| `handleize` | text to `a-slug-like-this` | `{{ title \| handleize }}` |
| `json` | value as safe JSON | `{{ product \| json }}` |
| `stars` | rating as ★★★★☆ | `{{ 4 \| stars }}` |

All standard Liquid filters work too: `upcase`, `default`, `size`, `date` and the rest.

> [!WARNING]
>
> Always pass store links through `url`. A bare `/products` link breaks inside the builder preview.

## Tags

| Tag | Use |
| - | - |
| `{% if %}` `{% for %}` `{% assign %}` | standard Liquid |
| `{% render 'name' %}` | include `snippets/name.liquid` |

Output is escaped by default, so text from settings is safe.

## Add a script

Put an `index.ts` next to `index.liquid`, exporting `mount(el, ctx)`. It runs after every render. Use it for sliders or menus.

## Cart and checkout

No API calls needed. Add `data-jm-*` attributes, or call `window.JoinMarkt`:

```html theme={null}
<button data-jm-add="{{ product.id }}">Add to cart</button>
<button data-jm-add="{{ product.id }}" data-jm-buy-now>Buy now</button>
<button data-jm-cart-open>Cart</button>
<button data-jm-checkout>Checkout</button>
```

| `window.JoinMarkt` | What it does |
| - | - |
| `cart.add(idOrSlug, { variant, quantity })` | add to cart |
| `cart.items`, `cart.count`, `cart.total` | read the cart |
| `cart.remove(lineId)`, `cart.setQuantity(lineId, n)` | change the cart |
| `cart.applyCoupon(code)`, `cart.removeCoupon()` | coupons |
| `cart.checkout()` | go to checkout |
| `formatPrice(amount)` | price in the buyer's currency |
| `navigate(path)` | go to another page |

Events on `document`: `jm:cart` when the cart changes, `jm:checkout-error` when checkout can't start. All attributes: [Commerce attributes](https://docs.joinmarkt.com/themes/commerce-attributes.md).

## Next

> [card] [Upload rules](https://docs.joinmarkt.com/themes/upload-rules.md)
>
> What gets kept, and every error.

> [card] [Commerce attributes](https://docs.joinmarkt.com/themes/commerce-attributes.md)
>
> Add to cart from plain markup.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.