Skip to content

Commerce Filters

Nimbu provides specialized filters for e-commerce functionality, making it easy to create shopping carts, checkout flows, and payment integrations.

Add to Cart

add_to_cart

Generate complete add-to-cart form:

liquid
{{ product | add_to_cart }}
<!-- Generates: Complete form with product, quantity, and submit button -->

{{ product | add_to_cart, btn_text: 'Add to Bag', btn_class: 'btn btn-primary' }}

{{ product | add_to_cart, default_quantity: 2, show_quantity: true }}

Parameters:

  • btn_text - Button label text
  • btn_class - CSS class for button
  • default_quantity - Initial quantity (default: 1)
  • show_quantity - Show quantity selector (default: false)
  • disabled - Disable the button

Generated HTML:

html
<form class="add-to-cart" method="post" action="/cart/add">
  <!-- CSRF token and product data -->
  <input type="hidden" name="product_id" value="123">
  <input type="hidden" name="quantity" value="1">
  <button type="submit" class="btn btn-primary">Add to Cart</button>
</form>

Custom Add to Cart

Build custom forms with more control:

liquid
<form action="/cart/add" method="post" class="product-form">
  {% csrf_token %}

  <input type="hidden" name="product_id" value="{{ product.id }}">

  <!-- Variant selector -->
  {% if product.variants.size > 1 %}
    <select name="variant_id" required>
      {% for variant in product.variants %}
        <option value="{{ variant.id }}" {% unless variant.available %}disabled{% endunless %}>
          {{ variant.title }} - {{ variant.price | money_with_currency }}
        </option>
      {% endfor %}
    </select>
  {% else %}
    <input type="hidden" name="variant_id" value="{{ product.variants.first.id }}">
  {% endif %}

  <!-- Quantity -->
  <input type="number"
         name="quantity"
         value="1"
         min="1"
         max="{{ product.inventory_quantity }}">

  <button type="submit" class="add-to-cart-btn">
    Add to Cart
  </button>
</form>

Cart Management

update_cart_item

Generate form to update cart item quantity:

liquid
{{ line_item | update_cart_item }}

{{ line_item | update_cart_item, btn_text: 'Update', btn_class: 'btn-sm' }}

Generated HTML:

html
<form class="update-cart-item" method="post" action="/cart/items/456/update">
  <input type="number" name="quantity" value="2" min="0">
  <button type="submit">Update</button>
</form>

delete_cart_item

Generate delete link for cart item:

liquid
{{ line_item | delete_cart_item }}

{{ line_item | delete_cart_item, delete: 'Remove', class: 'text-danger' }}

{{ line_item | delete_cart_item, confirm: 'Remove this item?' }}

Parameters:

  • delete - Link text (default: "Delete")
  • class - CSS class
  • confirm - Confirmation message

Checkout Integration

checkout_button

Generate checkout button with cart data:

liquid
{{ cart | checkout_button }}

{{ cart | checkout_button, text: 'Proceed to Checkout', class: 'btn-lg btn-success' }}

payment_form

Create payment method form:

liquid
{{ order | payment_form, provider: 'stripe' }}

{{ order | payment_form, provider: 'mollie', class: 'payment-section' }}

Supported Providers:

  • stripe - Stripe Checkout
  • mollie - Mollie Payments
  • paypal - PayPal Buttons

Coupon Helpers

apply_coupon_form

Generate coupon application form:

liquid
{{ cart | apply_coupon_form }}

{{ cart | apply_coupon_form, placeholder: 'Enter discount code', btn_text: 'Apply' }}

Generated Form:

html
<form class="apply-coupon" method="post" action="/cart/coupons">
  <input type="text" name="coupon_code" placeholder="Discount code">
  <button type="submit">Apply Coupon</button>
</form>

remove_coupon

Remove applied coupon:

liquid
{{ coupon | remove_coupon, text: 'Remove', class: 'text-sm' }}

Practical Examples

Product Page with Variants

liquid
<div class="product-page">
  <div class="product-images">
    <!-- Product gallery -->
  </div>

  <div class="product-details">
    <h1>{{ product.name }}</h1>

    <div class="price">
      {% if product.on_sale %}
        <span class="original-price">
          {{ product.compare_at_price | money_with_currency }}
        </span>
        <span class="sale-price">
          {{ product.price | money_with_currency }}
        </span>
      {% else %}
        {{ product.price | money_with_currency }}
      {% endif %}
    </div>

    <div class="product-form">
      <form action="/cart/add" method="post">
        {% csrf_token %}
        <input type="hidden" name="product_id" value="{{ product.id }}">

        {% if product.variants.size > 1 %}
          <div class="variant-selector">
            <label for="variant-select">Select Option:</label>
            <select name="variant_id" id="variant-select" required>
              {% for variant in product.variants %}
                <option
                  value="{{ variant.id }}"
                  data-price="{{ variant.price }}"
                  data-available="{{ variant.available }}"
                  {% unless variant.available %}disabled{% endunless %}>
                  {{ variant.title }}
                  {% unless variant.available %} - Sold Out{% endunless %}
                </option>
              {% endfor %}
            </select>
          </div>
        {% else %}
          <input type="hidden" name="variant_id" value="{{ product.variants.first.id }}">
        {% endif %}

        <div class="quantity-selector">
          <label for="quantity">Quantity:</label>
          <input
            type="number"
            name="quantity"
            id="quantity"
            value="1"
            min="1"
            max="{{ product.inventory_quantity }}">
        </div>

        <button
          type="submit"
          class="btn btn-primary btn-lg"
          {% unless product.available %}disabled{% endunless %}>
          {% if product.available %}
            Add to Cart
          {% else %}
            Sold Out
          {% endif %}
        </button>
      </form>
    </div>
  </div>
</div>

Shopping Cart Page

liquid
<div class="cart-page">
  <h1>Shopping Cart</h1>

  {% if cart.items.size > 0 %}
    <div class="cart-items">
      {% for item in cart.items %}
        <div class="cart-item">
          <div class="item-image">
            {% if item.product.images.first %}
              <img
                src="{{ item.product.images.first.url | filter, width: '150px', height: '150px', cropping: 'fill' }}"
                alt="{{ item.product.name }}">
            {% endif %}
          </div>

          <div class="item-details">
            <h3>
              <a href="{{ item.product.url }}">{{ item.product.name }}</a>
            </h3>

            {% if item.variant.title != 'Default Title' %}
              <p class="variant">{{ item.variant.title }}</p>
            {% endif %}

            <p class="price">
              {{ item.price | money_with_currency }}
            </p>
          </div>

          <div class="item-quantity">
            {{ item | update_cart_item, btn_text: 'Update', btn_class: 'btn-sm' }}
          </div>

          <div class="item-total">
            {{ item.line_price | money_with_currency }}
          </div>

          <div class="item-remove">
            {{ item | delete_cart_item, delete: '×', class: 'remove-btn' }}
          </div>
        </div>
      {% endfor %}
    </div>

    <div class="cart-summary">
      <div class="subtotal">
        <span>Subtotal:</span>
        <span>{{ cart.subtotal | money_with_currency }}</span>
      </div>

      {% if cart.total_discount > 0 %}
        <div class="discount">
          <span>
            Discount
            {% if cart.coupon %}
              ({{ cart.coupon.code }})
              {{ cart.coupon | remove_coupon, text: 'Remove' }}
            {% endif %}:
          </span>
          <span class="negative">
            -{{ cart.total_discount | money_with_currency }}
          </span>
        </div>
      {% endif %}

      <div class="shipping">
        <span>Shipping:</span>
        <span>
          {% if cart.shipping_cost > 0 %}
            {{ cart.shipping_cost | money_with_currency }}
          {% else %}
            Calculated at checkout
          {% endif %}
        </span>
      </div>

      <div class="total">
        <span>Total:</span>
        <span>{{ cart.total | money_with_currency }}</span>
      </div>

      <!-- Coupon form -->
      {% unless cart.coupon %}
        <div class="coupon-section">
          {{ cart | apply_coupon_form, placeholder: 'Discount code', btn_text: 'Apply' }}
        </div>
      {% endunless %}

      <!-- Checkout button -->
      <div class="checkout-actions">
        {{ cart | checkout_button, text: 'Proceed to Checkout', class: 'btn btn-lg btn-success' }}
      </div>
    </div>
  {% else %}
    <div class="empty-cart">
      <p>Your cart is empty.</p>
      <a href="/shop" class="btn btn-primary">Continue Shopping</a>
    </div>
  {% endif %}
</div>

Quick Add to Cart (AJAX)

liquid
<div class="product-card">
  <a href="{{ product.url }}">
    <img
      src="{{ product.images.first.url | filter, width: '400px', height: '400px', cropping: 'fill' }}"
      alt="{{ product.name }}">
  </a>

  <h3>
    <a href="{{ product.url }}">{{ product.name }}</a>
  </h3>

  <p class="price">{{ product.price | money_with_currency }}</p>

  <form
    action="/cart/add"
    method="post"
    class="quick-add-form"
    data-ajax-cart>

    {% csrf_token %}
    <input type="hidden" name="product_id" value="{{ product.id }}">
    <input type="hidden" name="variant_id" value="{{ product.variants.first.id }}">
    <input type="hidden" name="quantity" value="1">

    <button
      type="submit"
      class="btn btn-sm"
      {% unless product.available %}disabled{% endunless %}>
      {% if product.available %}
        Quick Add
      {% else %}
        Sold Out
      {% endif %}
    </button>
  </form>
</div>

<script>
document.querySelectorAll('[data-ajax-cart]').forEach(form => {
  form.addEventListener('submit', async (e) => {
    e.preventDefault();

    const formData = new FormData(form);
    const response = await fetch(form.action, {
      method: 'POST',
      body: formData,
      headers: {
        'Accept': 'application/json'
      }
    });

    if (response.ok) {
      const cart = await response.json();
      // Update cart UI
      updateCartCount(cart.item_count);
      showNotification('Added to cart!');
    }
  });
});
</script>

Checkout Page with Payment

liquid
<div class="checkout-page">
  <div class="checkout-content">
    <div class="checkout-steps">
      <!-- Step 1: Customer Info -->
      <div class="step" data-step="customer">
        <h2>Customer Information</h2>
        <!-- Customer form -->
      </div>

      <!-- Step 2: Shipping -->
      <div class="step" data-step="shipping">
        <h2>Shipping Address</h2>
        <!-- Shipping form -->
      </div>

      <!-- Step 3: Payment -->
      <div class="step" data-step="payment">
        <h2>Payment Method</h2>

        {{ order | payment_form, provider: 'stripe', class: 'payment-section' }}

        <!-- Or custom payment selector -->
        <div class="payment-methods">
          <label>
            <input type="radio" name="payment_method" value="stripe" checked>
            <span>Credit Card</span>
          </label>

          <label>
            <input type="radio" name="payment_method" value="paypal">
            <span>PayPal</span>
          </label>

          <label>
            <input type="radio" name="payment_method" value="mollie">
            <span>iDEAL / Bancontact</span>
          </label>
        </div>
      </div>
    </div>

    <!-- Order Summary Sidebar -->
    <div class="order-summary">
      <h3>Order Summary</h3>

      {% for item in cart.items %}
        <div class="summary-item">
          <span>{{ item.product.name }} × {{ item.quantity }}</span>
          <span>{{ item.line_price | money_with_currency }}</span>
        </div>
      {% endfor %}

      <div class="summary-subtotal">
        <span>Subtotal:</span>
        <span>{{ cart.subtotal | money_with_currency }}</span>
      </div>

      <div class="summary-shipping">
        <span>Shipping:</span>
        <span>{{ cart.shipping_cost | money_with_currency }}</span>
      </div>

      <div class="summary-total">
        <span>Total:</span>
        <span>{{ cart.total | money_with_currency }}</span>
      </div>
    </div>
  </div>
</div>

Best Practices

1. CSRF Protection

Always include CSRF token in forms:

liquid
<!-- ✅ Good: CSRF protected -->
<form action="/cart/add" method="post">
  {% csrf_token %}
  <!-- form fields -->
</form>

<!-- ❌ Bad: No CSRF protection -->
<form action="/cart/add" method="post">
  <!-- vulnerable to CSRF attacks -->
</form>

2. Validate Inventory

Check product availability:

liquid
<!-- ✅ Good: Check availability -->
<button
  type="submit"
  {% unless product.available %}disabled{% endunless %}>
  {% if product.available %}Add to Cart{% else %}Sold Out{% endif %}
</button>

<!-- ❌ Bad: No availability check -->
<button type="submit">Add to Cart</button>

3. User Feedback

Provide clear feedback for cart operations:

liquid
<form action="/cart/add" method="post" class="add-to-cart-form">
  <!-- form fields -->

  <div class="form-messages">
    {% if cart_error %}
      <div class="alert alert-danger">{{ cart_error }}</div>
    {% endif %}

    {% if cart_success %}
      <div class="alert alert-success">Added to cart!</div>
    {% endif %}
  </div>

  <button type="submit">Add to Cart</button>
</form>

4. Accessibility

Make forms accessible:

liquid
<form action="/cart/add" method="post">
  <label for="quantity">Quantity:</label>
  <input
    type="number"
    id="quantity"
    name="quantity"
    aria-label="Product quantity"
    min="1"
    value="1">

  <button type="submit" aria-label="Add {{ product.name }} to cart">
    Add to Cart
  </button>
</form>

Next Steps

Build powerful e-commerce experiences with Nimbu's commerce filters!