Skip to main content

Pricing

ChamevoJS calculates dynamic prices based on element properties and configurable pricing rules.

Price Sources

Prices come from two sources:

  1. Element prices — Each element can have a price parameter and colorPrices for per-color charges
  2. 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

FieldTypeDescription
propertyPricingPropertyWhat 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.
rulesArrayConditions to check, each with operator, value, and price.

Properties

PropertyDescriptionValue typeApplicable targets
textLengthCharacter count excluding whitespacenumber'#id', 'text', 'customtext', 'all' (only text elements yield a value)
linesLengthLine count (split by \n)number'#id', 'text', 'customtext', 'all' (only text elements yield a value)
fontSizeFont size in pixelsnumber'#id', 'text', 'customtext', 'all' (only text elements yield a value)
coveragePrint area coverage percentage (0–100). Evaluated per print area — target is not needed.number— (no target; runs once per print area)
elementsLengthCount of matching elementsnumberAny selector — counts whatever the target matches
colorsLengthCount of unique colors usednumberAny selector — the count is global, so the target does not affect the value
printAreasUsedCount 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

SelectorDescription
'#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 },
],
}

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

FieldTypeDescription
propertyPricingPropertyThe element property the rule evaluated (e.g. 'textLength').
targetstringHuman-readable description of the target (e.g. '#greeting', 'all text').
conditionstringHuman-readable condition string (e.g. '> 10 characters').
amountnumberThe 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.

PropTypeDefaultDescription
priceFormatPriceFormat{ currency: '&#36;%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