B2B & wholesaleTutorial
Prijs exclusief btw op de productpagina: een block dat met de variant meebeweegt
Diek Thunnissen17 jul 2025bijgewerkt 8 sep 20269 min leestijd
Een zakelijke koper rekent met netto prijzen, je shop toont bruto. Deze snippet zet de prijs exclusief btw onder de gewone prijs, met het percentage instelbaar in de theme editor, en rekent mee als de klant een andere variant kiest.

Verkoop je aan bedrijven én consumenten, dan ziet de zakelijke koper op je productpagina een prijs waar hij niets mee kan. Hij moet een budget verantwoorden of een offerte maken en wil het bedrag zonder btw zien. De vraag "hoeveel is het zonder btw?" komt dan per mail, en jij rekent het uit.
Dit block lost dat op aan de voorkant. Het toont de prijs exclusief btw direct onder de gewone prijs, met een instelbaar percentage, optioneel een label "Zakelijk" en de keuze om de prijs inclusief btw ernaast of eronder te laten staan. Kiest de klant een andere variant, dan rekent het block mee zonder pagina-refresh.
Het structurele antwoord voor een echte B2B-shop is native B2B met Companies en catalogs, waar prijzen per bedrijf exclusief btw kunnen zijn. Dit block is de lichte variant voor een gemengde shop op één storefront.
Stap 1: de snippet
Online Store > Themes > Actions > Edit code. Maak in de map Snippets een nieuwe snippet met de naam price-ex-btw en plak deze code:
279 regels codeToon code +Verberg code −
{% comment %} ⚡️ Prijs Exclusief BTW Snippet by Diek Thunnissen
- Werkt met echte product variants (variant-selects component)
- Werkt met product variations (verschillende producten)
- Detecteert beide systemen automatisch
{% endcomment %}
{%- liquid
# Bereken prijzen excl. BTW
assign btw_percentage = block.settings.btw_percentage | times: 1.0 | divided_by: 100.0
assign btw_multiplier = 1.0 | plus: btw_percentage
# Bereken de afrondfactor
assign decimal_places = block.settings.decimal_places | default: 2
assign rounding_factor = decimal_places | prepend: '1' | append: '0' | times: 1
# Voor product variations gebruiken we altijd het huidige product
assign target_product = product
# Verwerk prijzen voor het huidige product
if product.price_varies
assign regular_price_min = product.price_min | divided_by: btw_multiplier | times: rounding_factor | round | divided_by: rounding_factor
assign regular_price_max = product.price_max | divided_by: btw_multiplier | times: rounding_factor | round | divided_by: rounding_factor
else
assign regular_price = product.price | divided_by: btw_multiplier | times: rounding_factor | round | divided_by: rounding_factor
endif
# Als er een selected variant is, gebruik die prijs
if product.selected_or_first_available_variant
assign variant_price = product.selected_or_first_available_variant.price | divided_by: btw_multiplier | times: rounding_factor | round | divided_by: rounding_factor
endif
-%}
<style>
.zakelijk-btw-container {
display: flex;
align-items: center;
gap: 8px;
margin-top: {{ block.settings.spacing | default: 8 }}px;
margin-bottom: 12px;
}
.zakelijk-tag {
display: inline-block;
background-color: {{ block.settings.badge_bg_color | default: '#a18367' }};
color: {{ block.settings.badge_text_color | default: '#ffffff' }};
font-size: 12px;
padding: 3px 10px;
border-radius: {{ block.settings.badge_radius | default: 3 }}px;
}
.ex-btw-price {
font-size: {{ block.settings.font_size | default: 14 }}px;
color: {{ block.settings.price_color | default: '#000000' }};
font-weight: {{ block.settings.price_weight | default: 'bold' }};
}
.ex-btw-label {
color: {{ block.settings.label_color | default: '#707070' }};
font-weight: {{ block.settings.label_weight | default: 'normal' }};
font-size: {{ block.settings.font_size | default: 14 }}px;
{% if block.settings.label_style == 'italic' %}font-style: italic;{% endif %}
}
{% if block.settings.custom_css != blank %}
{{ block.settings.custom_css }}
{% endif %}
</style>
<div class="zakelijk-btw-container {{ block.settings.custom_class }}" id="btw-price-container">
{% if block.settings.show_badge %}
<span class="zakelijk-tag">{{ block.settings.badge_text | default: 'Zakelijk' }}</span>
{% endif %}
<span class="ex-btw-label">{{ block.settings.label_text | default: 'Excl. BTW:' }}</span>
{%- if product.price_varies -%}
<span class="ex-btw-price" id="btw-price-display">
{%- if block.settings.price_format == 'from' -%}
{{ block.settings.from_text | default: 'Vanaf' }} {{ regular_price_min | money }}
{%- else -%}
{{ regular_price_min | money }}{{ block.settings.range_separator | default: ' - ' }}{{ regular_price_max | money }}
{%- endif -%}
</span>
{%- else -%}
<span class="ex-btw-price" id="btw-price-display">
{%- if variant_price -%}
{{ variant_price | money }}
{%- else -%}
{{ regular_price | money }}
{%- endif -%}
</span>
{%- endif -%}
{% if block.settings.show_inc_btw %}
{% if block.settings.inc_btw_placement == 'inline' %}
<span class="ex-btw-label" style="margin-left: 8px;">{{ block.settings.inc_btw_label | default: 'Incl. BTW:' }}</span>
<span class="ex-btw-price" id="inc-btw-price-display">
{%- if product.price_varies -%}
{%- if block.settings.price_format == 'from' -%}
{{ block.settings.from_text | default: 'Vanaf' }} {{ product.price_min | money }}
{%- else -%}
{{ product.price_min | money }}{{ block.settings.range_separator | default: ' - ' }}{{ product.price_max | money }}
{%- endif -%}
{%- else -%}
{%- if product.selected_or_first_available_variant -%}
{{ product.selected_or_first_available_variant.price | money }}
{%- else -%}
{{ product.price | money }}
{%- endif -%}
{%- endif -%}
</span>
{% endif %}
{% endif %}
</div>
{% if block.settings.show_inc_btw and block.settings.inc_btw_placement == 'under' %}
<div class="zakelijk-btw-container {{ block.settings.custom_class }}" style="margin-top: 4px;">
<span class="ex-btw-label">{{ block.settings.inc_btw_label | default: 'Incl. BTW:' }}</span>
<span class="ex-btw-price" id="inc-btw-price-display-under">
{%- if product.price_varies -%}
{%- if block.settings.price_format == 'from' -%}
{{ block.settings.from_text | default: 'Vanaf' }} {{ product.price_min | money }}
{%- else -%}
{{ product.price_min | money }}{{ block.settings.range_separator | default: ' - ' }}{{ product.price_max | money }}
{%- endif -%}
{%- else -%}
{%- if product.selected_or_first_available_variant -%}
{{ product.selected_or_first_available_variant.price | money }}
{%- else -%}
{{ product.price | money }}
{%- endif -%}
{%- endif -%}
</span>
</div>
{% endif %}
{% comment %} JavaScript voor variant price updates{% endcomment %}
{% if template == 'product' %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const BTW_MULTIPLIER = {{ btw_multiplier }};
const btwPriceDisplay = document.getElementById('btw-price-display');
const incBtwPriceDisplay = document.getElementById('inc-btw-price-display');
const incBtwPriceDisplayUnder = document.getElementById('inc-btw-price-display-under');
if (!btwPriceDisplay) return;
// Functie om BTW prijzen te updaten
function updateBTWPrices(variantId) {
if (!variantId) return;
// Zoek variant data uit het JSON script in variant-selects
const variantScript = document.querySelector('variant-selects script[data-selected-variant]');
let variantData = null;
if (variantScript) {
try {
const selectedVariant = JSON.parse(variantScript.textContent);
if (selectedVariant && selectedVariant.id.toString() === variantId.toString()) {
variantData = selectedVariant;
}
} catch (e) {
console.log('Error parsing selected variant:', e);
}
}
// Als geen data van script, zoek alle variants
if (!variantData) {
// Zoek in window object
if (window.productData && window.productData.variants) {
variantData = window.productData.variants.find(v => v.id.toString() === variantId.toString());
}
// Zoek in andere mogelijke locaties
if (!variantData && window.product && window.product.variants) {
variantData = window.product.variants.find(v => v.id.toString() === variantId.toString());
}
}
if (variantData && variantData.price) {
const variantPrice = parseInt(variantData.price);
const exBtwPrice = Math.round(variantPrice / BTW_MULTIPLIER);
// Update BTW prijs
if (typeof Shopify !== 'undefined' && Shopify.formatMoney) {
btwPriceDisplay.textContent = Shopify.formatMoney(exBtwPrice, "{{ shop.money_format }}");
if (incBtwPriceDisplay) {
incBtwPriceDisplay.textContent = Shopify.formatMoney(variantPrice, "{{ shop.money_format }}");
}
if (incBtwPriceDisplayUnder) {
incBtwPriceDisplayUnder.textContent = Shopify.formatMoney(variantPrice, "{{ shop.money_format }}");
}
} else {
// Fallback zonder Shopify.formatMoney
const formattedExBtw = '€' + (exBtwPrice / 100).toFixed(2).replace('.', ',');
const formattedInc = '€' + (variantPrice / 100).toFixed(2).replace('.', ',');
btwPriceDisplay.textContent = formattedExBtw;
if (incBtwPriceDisplay) incBtwPriceDisplay.textContent = formattedInc;
if (incBtwPriceDisplayUnder) incBtwPriceDisplayUnder.textContent = formattedInc;
}
}
}
// Luister naar variant-selects component
const variantSelects = document.querySelector('variant-selects');
if (variantSelects) {
// Luister naar radio button changes (swatch picker)
const radioInputs = variantSelects.querySelectorAll('input[type="radio"]');
radioInputs.forEach(input => {
input.addEventListener('change', function() {
setTimeout(() => {
// Check of er een hidden input is met variant ID
const hiddenInput = document.querySelector('input[name="id"][type="hidden"], select[name="id"]');
if (hiddenInput && hiddenInput.value) {
updateBTWPrices(hiddenInput.value);
}
}, 100);
});
});
// Luister naar select changes (dropdown picker)
const selectInputs = variantSelects.querySelectorAll('select');
selectInputs.forEach(select => {
select.addEventListener('change', function() {
setTimeout(() => {
const hiddenInput = document.querySelector('input[name="id"][type="hidden"], select[name="id"]');
if (hiddenInput && hiddenInput.value) {
updateBTWPrices(hiddenInput.value);
}
}, 100);
});
});
// Observeer DOM changes voor dynamische updates
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' || mutation.type === 'attributes') {
const hiddenInput = document.querySelector('input[name="id"][type="hidden"], select[name="id"]');
if (hiddenInput && hiddenInput.value) {
updateBTWPrices(hiddenInput.value);
}
}
});
});
observer.observe(variantSelects, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-selected-variant']
});
}
// Luister naar custom events
document.addEventListener('variant:change', function(event) {
if (event.detail && event.detail.variant && event.detail.variant.id) {
updateBTWPrices(event.detail.variant.id);
}
});
// Fallback: observeer prijs wijzigingen
const priceElement = document.querySelector('.price, .product__price, [data-price]');
if (priceElement) {
const priceObserver = new MutationObserver(() => {
setTimeout(() => {
const hiddenInput = document.querySelector('input[name="id"][type="hidden"], select[name="id"]');
if (hiddenInput && hiddenInput.value) {
updateBTWPrices(hiddenInput.value);
}
}, 50);
});
priceObserver.observe(priceElement, { childList: true, subtree: true });
}
});
</script>
{% endif %}Drie delen. Het Liquid-blok bovenin rekent de prijzen exclusief btw uit, ook voor producten met een prijsbereik. De CSS zet de opmaak uit de block-instellingen. Het JavaScript onderaan luistert naar de variant-picker van het thema en herrekent de prijs bij elke wissel. Het probeert meerdere plekken om de variantdata te vinden, omdat thema's die verschillend opslaan.
Stap 2: het block in main-product.liquid
Open main-product.liquid en zoek {%- case block.type -%}. Direct eronder:
{%- when 'price_ex_btw' -%}
<div class="product-info__block product-info__block--sm product-price-ex-btw" {{ block.shopify_attributes }}>
{% render 'price-ex-btw', product: product, block: block, use_variant: true, current_variant: current_variant %}
</div>Stap 3: de instellingen in het schema
Scroll naar {% schema %} en plak dit direct na "blocks": [ :
229 regels codeToon code +Verberg code −
{
"type": "price_ex_btw",
"name": "⚡️ Prijs Excl. BTW",
"limit": 1,
"settings": [
{
"type": "header",
"content": "⚡️ Prijs Exclusief BTW by Diek Thunnissen",
"info": "Voeg prijzen exclusief BTW toe aan je product pagina."
},
{
"type": "range",
"id": "btw_percentage",
"min": 1,
"max": 30,
"step": 1,
"unit": "%",
"label": "BTW percentage",
"default": 21
},
{
"type": "text",
"id": "label_text",
"label": "Label tekst",
"default": "Excl. BTW:"
},
{
"type": "checkbox",
"id": "show_inc_btw",
"label": "Toon ook prijs incl. BTW",
"default": true
},
{
"type": "text",
"id": "inc_btw_label",
"label": "Incl. BTW label",
"default": "Incl. BTW:"
},
{
"type": "select",
"id": "inc_btw_placement",
"label": "Incl. BTW weergave",
"options": [
{
"value": "under",
"label": "Onder excl. BTW"
},
{
"value": "inline",
"label": "Naast excl. BTW"
}
],
"default": "under"
},
{
"type": "select",
"id": "price_format",
"label": "Prijsweergave bij prijsbereik",
"options": [
{
"value": "from",
"label": "Vanaf"
},
{
"value": "range",
"label": "Bereik (min-max)"
}
],
"default": "from"
},
{
"type": "text",
"id": "from_text",
"label": "Vanaf tekst",
"default": "Vanaf"
},
{
"type": "text",
"id": "range_separator",
"label": "Bereik separator",
"default": " - "
},
{
"type": "header",
"content": "Badge"
},
{
"type": "checkbox",
"id": "show_badge",
"label": "Toon badge",
"default": false
},
{
"type": "text",
"id": "badge_text",
"label": "Badge tekst",
"default": "Zakelijk"
},
{
"type": "color",
"id": "badge_bg_color",
"label": "Badge achtergrondkleur",
"default": "#f2f2f2"
},
{
"type": "color",
"id": "badge_text_color",
"label": "Badge tekstkleur",
"default": "#555555"
},
{
"type": "range",
"id": "badge_radius",
"min": 0,
"max": 20,
"step": 1,
"unit": "px",
"label": "Badge hoekradius",
"default": 4
},
{
"type": "header",
"content": "Styling"
},
{
"type": "range",
"id": "spacing",
"min": 0,
"max": 40,
"step": 2,
"unit": "px",
"label": "Verticale ruimte",
"default": 8
},
{
"type": "range",
"id": "font_size",
"min": 10,
"max": 24,
"step": 1,
"unit": "px",
"label": "Lettergrootte",
"default": 14
},
{
"type": "color",
"id": "label_color",
"label": "Label kleur",
"default": "#707070"
},
{
"type": "color",
"id": "price_color",
"label": "Prijs kleur",
"default": "#000000"
},
{
"type": "select",
"id": "label_weight",
"label": "Label gewicht",
"options": [
{
"value": "normal",
"label": "Normaal"
},
{
"value": "bold",
"label": "Vet"
},
{
"value": "500",
"label": "Medium"
}
],
"default": "normal"
},
{
"type": "select",
"id": "label_style",
"label": "Label stijl",
"options": [
{
"value": "normal",
"label": "Normaal"
},
{
"value": "italic",
"label": "Cursief"
}
],
"default": "normal"
},
{
"type": "select",
"id": "price_weight",
"label": "Prijs gewicht",
"options": [
{
"value": "normal",
"label": "Normaal"
},
{
"value": "bold",
"label": "Vet"
},
{
"value": "500",
"label": "Medium"
}
],
"default": "bold"
},
{
"type": "header",
"content": "Geavanceerd"
},
{
"type": "text",
"id": "custom_class",
"label": "Custom CSS class"
},
{
"type": "liquid",
"id": "custom_css",
"label": "Custom CSS",
"info": "Gebruik de .zakelijk-btw-container class als selector"
}
]
},Het block gebruiken
Online Store > Themes > Customize, open een productpagina, Add block onder Product information en kies Prijs Excl. BTW. Direct onder de prijs is de logische plek. Rechts staan alle instellingen.
Drie configuraties
Standaard zakelijk: percentage op 21, "Toon ook prijs incl. BTW" aan, weergave "Onder excl. BTW". Beide prijzen netjes onder elkaar.
Subtiel: lettergrootte 12px, labelkleur lichtgrijs (#999999), prijskleur donkergrijs (#666666). De informatie staat er, maar trekt de aandacht niet weg van de hoofdprijs.
Met badge: zet de badge "Zakelijk" aan in een kleur uit je huisstijl en geef de container wat lucht via het Custom CSS-veld:
.zakelijk-btw-container {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
}Verschillende btw-tarieven
Verkoop je producten met 21% en met 9% (voeding, boeken)? Maak per tarief een product template aan en stel in elk template het juiste percentage in op het block. Wijs de template toe per product.
Als het niet werkt
Meestal een typfout in de JSON van het schema: één ontbrekende komma en Shopify weigert het hele bestand. Werkt de variantprijs niet mee? Dan slaat je thema de variantdata ergens anders op dan de snippet zoekt. Kijk in de inspector welk element bij een variantwissel verandert en pas de selectors in het script daarop aan.
Bij een thema-update blijft de snippet staan, maar de block-code in main-product.liquid en het schema moet je opnieuw toevoegen. Bewaar die twee stukken ergens buiten het thema.
Veelgestelde vragen.
- Kan de klant afrekenen exclusief btw?
- Niet met dit block. Het toont alleen een berekende prijs. Btw-vrij afrekenen (bijvoorbeeld bij een geldig btw-nummer binnen de EU) regel je via Shopify Markets en belastinginstellingen, of via native B2B met Companies.
- Werkt dit met Shopify Markets en meerdere valuta's?
- De berekening deelt de getoonde prijs door de btw-factor, dus in elke valuta klopt de verhouding. Let wel op het btw-percentage: dat verschilt per land en staat hier vast per template.
- Wat als je thema geen variant-selects-element heeft?
- Dan luistert het script naar het variant:change-event en observeert het de prijs-elementen als fallback. Werkt dat ook niet, dan moet je de selector aanpassen naar het element dat jouw thema gebruikt voor de variant-id.
Diek ThunnissenFounder & lead developer. Bouwt, verbetert en migreert de Shopify-laag voor DTC- en B2B-merken. LinkedInPlan een gesprek →

