Pricing
ChamevoJS calculates dynamic prices based on element properties and configurable pricing rules.
Price Sources
Prices come from two sources:
- Element prices — Each element can have a
priceparameter andcolorPricesfor per-color charges - Pricing rules — Configurable rules that calculate additional charges based on element properties
Total = (elementPrices + pricingRulesPrice) x orderQuantity
Element Pricing
Set a price directly on elements:
{
type: 'image',
source: '/images/premium-graphic.png',
title: 'Premium Graphic',
parameters: {
price: 5.00,
colorPrices: {
'#ff0000': 2.00, // Extra $2 for red
'#ffd700': 3.00, // Extra $3 for gold
},
},
}
colorPrices keys are matched case-insensitively (with or without the leading #). The surcharge applies to text, raster (PNG) images, and SVG images.
For multi-path SVGs, the charge is the sum over the distinct colors used across the paths — a logo that reuses the same premium color on several paths is charged for it once. If an element has no colorPrices of its own, the customizer falls back to a global map set via textParameters.colorPrices.
Pricing Rules
Pricing rules evaluate element properties (text length, font size, element count, etc.) against conditions and add charges when conditions match.
Configuration
Pass rules via the pricingRules option — an array of rule groups:
const options = {
pricingRules: [
{
property: 'textLength',
target: { elements: '#greeting' },
type: 'any',
rules: [
{ operator: '>', value: 20, price: 5 },
{ operator: '>', value: 10, price: 2 },
],
},
{
property: 'fontSize',
target: { elements: 'text' },
type: 'all',
rules: [
{ operator: '>=', value: 24, price: 3 },
],
},
],
};
Rule Group Fields
| Field | Type | Description |
|---|---|---|
property | PricingProperty | What to measure on each target element. See Properties below. |
target | { elements?: string } | Which elements to evaluate. See Target selectors below. Not used for coverage (applies to all print areas automatically) or printAreasUsed (design-level count). |
type | 'any' | 'all' | 'any' = first matching rule wins (if/else). 'all' = all matching rules accumulate. |
rules | Array | Conditions to check, each with operator, value, and price. |
Properties
| Property | Description | Value type | Applicable targets |
|---|---|---|---|
textLength | Character count excluding whitespace | number | '#id', 'text', 'customtext', 'all' (only text elements yield a value) |
linesLength | Line count (split by \n) | number | '#id', 'text', 'customtext', 'all' (only text elements yield a value) |
fontSize | Font size in pixels | number | '#id', 'text', 'customtext', 'all' (only text elements yield a value) |
coverage | Print area coverage percentage (0–100). Evaluated per print area — target is not needed. | number | — (no target; runs once per print area) |
elementsLength | Count of matching elements | number | Any selector — counts whatever the target matches |
colorsLength | Count of unique colors used | number | Any selector — the count is global, so the target does not affect the value |
printAreasUsed | Count of print areas containing at least one element, across all views. Evaluated once per design — target is not needed. | number | — (no target; runs once per design) |
Target Selectors
| Selector | Description |
|---|---|
'#title' | Specific element by title (e.g. '#greeting') |
'text' | All text elements |
'image' | All image elements |
'all' | All elements |
'customtext' | User-added text elements only |
'customimage' | User-added image elements only |
Upload zone elements are always excluded from pricing rules.
Operators
=, >, <, >=, <=
Each rule compares the evaluated property against a numeric value:
{ operator: '>', value: 20, price: 10 }
Evaluation Mode
-
type: 'any'— Rules are checked in order; the first match wins. Use for tiered pricing (order rules from most expensive to least):{
property: 'textLength',
target: { elements: 'text' },
type: 'any',
rules: [
{ operator: '>', value: 50, price: 10 }, // checked first
{ operator: '>', value: 20, price: 5 },
{ operator: '>', value: 10, price: 2 }, // checked last
],
} -
type: 'all'— Every matching rule adds its price. Use when charges should stack:{
property: 'fontSize',
target: { elements: 'text' },
type: 'all',
rules: [
{ operator: '>=', value: 24, price: 3 },
{ operator: '>=', value: 48, price: 5 }, // both apply if fontSize >= 48
],
}
Coverage Rules
Coverage rules evaluate print area fill percentage. They don't need a target — the rule is applied to every print area automatically:
{
property: 'coverage',
type: 'any',
rules: [
{ operator: '>', value: 50, price: 8 },
{ operator: '>', value: 25, price: 3 },
],
}
Print Locations (printAreasUsed)
printAreasUsed counts how many print areas contain at least one element — across all views of the product (front + back + sleeve = 3 print locations). Use it for per-location surcharges, the most common apparel upcharge:
{
property: 'printAreasUsed',
type: 'all',
rules: [
{ operator: '>=', value: 2, price: 4 }, // +$4 once a 2nd location is printed
{ operator: '>=', value: 3, price: 4 }, // +$4 more for a 3rd
],
}
Unlike coverage (which runs once per print area), printAreasUsed is evaluated once per design, no matter how many views or print areas the product has. Define it in the main pricingRules option; empty print areas and upload-zone placeholders don't count as used.
Price Format
Configure how prices are displayed:
const options = {
priceFormat: {
currency: '$%d', // %d is replaced with the formatted number
decimalSep: '.',
thousandSep: ',',
},
};
Events
Listen for price changes:
chamevo.on('priceChange', ({ price, singleProductPrice, pricingRulesPrice, breakdown, orderQuantity }) => {
console.log(`Base: $${singleProductPrice}`);
console.log(`Rules: +$${pricingRulesPrice}`);
console.log(`Quantity: x${orderQuantity}`);
console.log(`Total: $${price}`);
console.log('Breakdown:', breakdown);
});
Pricing Breakdown
Every priceChange event includes a breakdown array — one PricingLineItem per matched pricing rule. Use it to show customers exactly what drives the price.
PricingLineItem Type
| Field | Type | Description |
|---|---|---|
property | PricingProperty | The element property the rule evaluated (e.g. 'textLength'). |
target | string | Human-readable description of the target (e.g. '#greeting', 'all text'). |
condition | string | Human-readable condition string (e.g. '> 10 characters'). |
amount | number | The price contribution of this line item. |
getPricingBreakdown() Method
Call chamevo.getPricingBreakdown() at any time to get the current breakdown without waiting for a priceChange event:
const breakdown = chamevo.getPricingBreakdown();
breakdown.forEach(item => {
console.log(`${item.property}: ${item.condition} → +$${item.amount.toFixed(2)}`);
});
<cv-price-breakdown> Component
Use the built-in web component for a ready-made breakdown display. The component auto-updates by listening to the cvPriceChange event — no manual wiring needed:
<cv-price-breakdown></cv-price-breakdown>
Optionally set priceFormat to match your currency:
const breakdownEl = document.querySelector('cv-price-breakdown');
breakdownEl.priceFormat = { currency: '€%d', decimalSep: ',', thousandSep: '.' };
The component displays up to four sections:
- Base price — from element prices, shown when > 0
- Surcharges — pricing rule line items with labels, conditions, and amounts
- Subtotal + Quantity — shown when order quantity > 1
- Total — final price including quantity
When there are no element prices and no matched pricing rules, the component shows an empty state.
| Prop | Type | Default | Description |
|---|---|---|---|
priceFormat | PriceFormat | { currency: '$%d' } | Currency formatting. |
The component reads all other data (breakdown, basePrice, quantity, total) from the cvPriceChange event automatically.
Custom Rendering
For full control over the display, use the raw breakdown data:
chamevo.on('priceChange', ({ price, breakdown }) => {
const list = document.getElementById('price-breakdown');
list.innerHTML = '';
breakdown.forEach(item => {
const li = document.createElement('li');
li.textContent = `${item.condition} — +$${item.amount.toFixed(2)}`;
list.appendChild(li);
});
document.getElementById('total-price').textContent = `$${price.toFixed(2)}`;
});
Order Quantity
chamevo.setOrderQuantity(5); // Multiplies total price by 5
Per-View / Per-Print-Area maxPrice
Limit the maximum price per view or print area:
// View-level
{
title: 'Front',
options: { maxPrice: 50 },
elements: [...],
}
// Print area level
{
printingBox: { ... },
printProfile: { maxPrice: 25 },
}
maxPrice: -1 means no limit (default).
Next Steps
- Events — Handling
priceChangeevents - Options Reference —
priceFormat,pricingRules,maxPrice