Pricing Overview
Mimeeq's pricing system has a lot of moving parts. This page explains how the system is structured, how prices are resolved at runtime, and which integration approach applies to your use case. Understanding this model will save you significant debugging time when prices don't appear as expected.
This page focuses on how pricing works from a developer perspective. For the admin-side configuration (creating price lists, importing MCPs, building public price groups), see Guide to Setting Up Pricing and Advanced Pricing Integration in the help center.
Pricing Structure
The pricing hierarchy runs from raw cost data through progressively higher-level abstractions until it reaches your embed:
Currency (symbol, exchange rate relative to default)
└── Market (one per currency)
└── Market Cost Prices / MCP (code → cost + RRP per qty, per market)
└── Price List (transforms MCPs into final prices)
├── Calculated — applies multipliers & rounding to MCP values
├── Fixed — explicitly defined prices per code (imported or manual)
└── Linked — inherits RRP/Sale from another price list + adds manipulations
└── Public Price List Group (collects price lists; sets currency, price type, VAT)
└── Embed Template (assigns public price group for guest users)
Each price list has a validity period and an active/inactive status. Expired or inactive price lists are silently excluded.
Price List Types
Calculated price lists derive final prices from MCPs using multipliers and rounding rules you configure per price field (Cost, RRP, Sale). This is the most common type.
Fixed price lists let you provide exact RRP and Sale prices per code, bypassing MCP calculations. Only the Cost field uses an MCP multiplier.
Linked price lists connect to a Fixed price list and apply additional multipliers on top — useful for channel-specific or currency-adjusted variants without duplicating base data.
Calculated and Fixed price lists can also have a scope that restricts which products they apply to: all products, only products you own, only shared products, products from specific owners, or a manually selected set. This matters particularly when you share products with other Mimeeq customers or receive shared products.
Standard vs Promo Price Lists
Each price list is classified as either Standard or Promo. When a Public Price List Group contains at least one of each, the configurator automatically displays compare pricing — the Promo price is shown as the current price, and the Standard price appears crossed out. If only one type is present, compare pricing is not shown.
This is reflected in the PriceData payload via comparePrice and unitComparePrice, which are only present when compare pricing is active.
window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (!newValue) return;
const { price, unitPrice, comparePrice, unitComparePrice, currency } = newValue;
// comparePrice and unitComparePrice are undefined when no promo pricing is active
if (comparePrice) {
showComparePrice(comparePrice, price, currency);
} else {
showRegularPrice(price, currency);
}
});
For admin setup of promo price lists, see How to Set Up Compare Prices.
Price Types
Every price list exposes up to four price types, which control which price field is returned:
| Type | Default label | Description |
|---|---|---|
RRP | RRP | Recommended retail price |
SALE | Sale | Discounted selling price |
COST | Cost | Internal cost/wholesale price |
BLANK | — | No price; effectively hides pricing |
The labels are fully configurable in the admin panel, so a deployment might display "Discounted" instead of "Sale" or "Wholesale" instead of "Cost." The underlying type constants remain fixed.
A Tier 1 (customer admin) user can disable specific types per company and set a default. When only one type is available, no type selector is shown in the UI.
Guest users see whichever type was set on their Public Price List Group — they cannot change it. Authenticated Tier 2 users (company members) see the type assigned to their company, it may be changed by them if they have more then one type available. Tier 1 users and global admins can switch between all available types across any company.
The currently active price type is available through the pricing.priceType observer:
window.mimeeqApp.observers.pricing.priceType.subscribe(({ newValue }) => {
// newValue is null/undefined for guest users (type is fixed, not exposed)
if (newValue) {
console.log(newValue.type); // 'RRP' | 'SALE' | 'COST' | 'BLANK'
console.log(newValue.label); // Display label, e.g. 'Wholesale'
}
});
Available price type options for the current user are exposed through pricing.priceOptions:
window.mimeeqApp.observers.pricing.priceOptions.subscribe(({ newValue }) => {
if (newValue && newValue.length > 1) {
// More than one type available — render a selector
renderPriceTypeSelector(newValue);
}
});
How Price Codes Are Resolved
The price the user sees is calculated from price codes — identifiers that map to entries in your MCPs or Fixed price lists. Understanding how codes are resolved is important when building custom UIs that need to display price breakdowns.
Product-level price codes
Each product has one or more price code groups configured in its admin. A group contains one or more code patterns, which can be:
- Static — a literal code string, e.g.
CHAIR-BASE - Dynamic — a template that resolves based on selected options, e.g.
FABRIC/{SeatFinishes#priceCode} - Formula-based — a calculated expression, e.g. for engraving prices that scale with character count
Each option in an option set can have a price code and an optional fallback price code. When a dynamic pattern resolves, the system checks the primary code first; if no price is found, it falls back to the fallback code.
Code Group Scope
Each price code group has a scope that controls which of the group's resolved codes are kept and used in the final price calculation.
The resolution happens in two steps:
- Each code pattern in the group is resolved against all applicable price lists. When multiple price lists match the same code, the cheapest match is used. This step always picks the cheapest price list — it is not controlled by the scope.
- The scope is then applied to the resulting set of resolved codes to decide which ones contribute to the group's price.
| Scope | Behaviour |
|---|---|
ALL | All resolved codes are kept and their prices summed (default) |
MIN | Only the single lowest-priced resolved code is kept |
MAX | Only the single highest-priced resolved code is kept |
AVERAGE | All resolved codes are averaged into a single value |
COMPONENT | For hybrid modular products — applies the above logic per component |
Codes that resolve to a blank price (no matching entry) are excluded before MIN, MAX, and AVERAGE are evaluated. If all codes resolve blank, the group contributes zero.
Additional price codes
Options within option sets can also carry additional price codes that are resolved independently of the product-level groups and then added to the total. This lets you price individual option selections separately from the base product. A product can use only additional price codes, only product-level groups, or both.
The final displayed price is the sum of the product-level group result and all additional price code results.
Quantity and Order Constraints
Quantity affects pricing in two ways:
Quantity breaks — Price lists can define multiple price entries for the same code at different quantity thresholds. The system uses a threshold model: once a quantity tier is reached, all units are priced at that tier's rate (not graduated). These are surfaced in the PriceData.levels array:
interface PriceLevel {
quantityMin: number;
quantityMax?: number; // undefined on the last tier
price: number; // unit price at this tier
pricePart: number; // one-time price portion (if any)
}
MOQ and IOC — Products can enforce a minimum order quantity (MOQ) and an incremental order quantity (IOC). The quantity selector enforces these constraints: the user cannot go below MOQ, and after MOQ is met, quantity can only increase in IOC increments. For example, MOQ=10 + IOC=2 means valid quantities are 10, 12, 14, 16...
These constraints are configured per product in the admin panel (Pricing and Codes tab) and are not currently controllable via rules. They affect the qty value in event payloads like mimeeq-add-to-cart. See MOQ & IOC for admin setup.
One-time prices
A price code can be flagged as "Ignore Product Quantity." The charge for that code applies once per order regardless of quantity and is distributed across units for display purposes. The PriceData object exposes this separately:
interface PriceData {
price: number; // Total: (unitPrice × qty) + oneTimePrice
unitPrice: number; // Per-unit price at the current quantity tier
oneTimePrice?: number; // Fixed charge, not multiplied by qty
currency: string;
}
When a one-time price is present, the configurator shows an informational message on the finish screen explaining the pricing structure to customers.
How User Context Affects Pricing
The price a user sees depends on their authentication state:
Guest users see the price from the Public Price List Group assigned to the embed template. The price type and currency are fixed by that group's settings. Guests cannot change the price type.
Tier 2 users (company members who log in) automatically see pricing from the price lists assigned to their company. If no price list is assigned to their company, they see "-" — even if a public price group is configured. If no public price group is configured but their company has price lists, they see prices while guests do not. The switch happens automatically on login — no developer action required.
Tier 1 users and global admins can switch between any company using the dealer selector. This is exposed through the pricing.dealers and pricing.selectedDealer observers. "Dealers" in the observer context means companies.
// Render a company (dealer) selector for Tier 1 users
window.mimeeqApp.observers.pricing.dealers.subscribe(({ newValue }) => {
if (newValue && newValue.length > 0) {
renderDealerSelector(newValue); // each item: { id, name, label }
}
});
window.mimeeqApp.observers.pricing.selectedDealer.subscribe(({ newValue }) => {
if (newValue) {
highlightActiveDealer(newValue.id);
}
});
Reading Prices in the Browser
The pricing.prices observer is the primary way to consume price data client-side. It emits whenever any pricing-relevant state changes: option selection, quantity change, company switch, or price type change.
window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (!newValue || newValue.price === 0) {
// price === 0 means no pricing is configured, or BLANK price type is active
// The configurator UI displays '-' in this case
showNoPrice();
return;
}
updatePriceDisplay({
total: newValue.price,
unit: newValue.unitPrice,
currency: newValue.currency,
deliveryTime: newValue.deliveryTime,
levels: newValue.levels, // quantity break tiers, if any
oneTimePrice: newValue.oneTimePrice, // if one-time charges apply
comparePrice: newValue.comparePrice, // if promo pricing is active
});
});
pricing.isPriceLoading emits true while a price fetch is in progress — useful for showing loading states in custom UIs.
When no pricing is configured at all, pricing.prices emits { price: 0 }. The built-in UI shows "-". Setting hide-price on the embed or template only hides the price panel in the configurator UI — the observer still emits, and the price still appears in basket, PDF output, and quotations.
For modular products, per-component pricing is available through pricing.modularPrices (a map of configuration code → PriceData) and the summed total through pricing.totalPriceModular.
The REST API Pricing Endpoint
Use the /get-product-price-info endpoint to fetch prices server-side — for example, when implementing custom pricing or when you need prices before rendering the configurator.
The endpoint accepts a configuration short code (or scene short code for modular products) plus one or more resolution parameters. If multiple are provided, the highest-priority one wins:
| Priority | Parameter | Resolves using |
|---|---|---|
| 1 (highest) | companyId | All price lists assigned to that company |
| 2 | priceListIds | Specific price list IDs you supply — returns cheapest match |
| 3 | priceListGroup | A Public Price List Group slug |
| 4 | templateId | The public price group (or language, if legacy) assigned to that embed template |
| 5 (lowest) | lang | Language-assigned price lists (legacy — not available for new setups) |
Additional parameters:
| Parameter | Description |
|---|---|
shortCode | Required. Configuration short code or scene short code |
quantity | Required. Number of units |
priceType | RRP, SALE, or COST |
vatType | INCL_VAT or EXCL_VAT |
When to use each mode
By companyId — when you manage your own authentication and want to return user-specific pricing based on their company assignment in Mimeeq. Store the company ID alongside your user record and pass it on login.
By priceListIds — when you want full control over which price lists to query from your own code, rather than relying on Mimeeq's admin assignments. Calling the endpoint separately for each price list ID lets you compare prices across lists and display the difference as a discount.
By priceListGroup — when you want to use prices from a named public price group without the user being authenticated. Equivalent to what guests see in the configurator.
By templateId — simplest option when you want the price that corresponds to a specific embed's configuration exactly as set up in the admin.
By lang — legacy only. Not available for accounts created after the public price list groups feature was introduced.
Each parameter combination returns one price response. To compare prices across multiple price lists or companies, make separate API calls for each.
// Server-side: fetch price for a specific company
async function getCompanyPrice(shortCode, companyId, quantity) {
const params = new URLSearchParams({ shortCode, companyId, quantity, priceType: 'RRP' });
const response = await fetch(`https://mimeeqapi.com/get-product-price-info?${params}`, {
headers: { 'X-API-KEY': process.env.MIMEEQ_API_KEY },
});
return response.json();
}
See the API Reference for the full request/response schema and the Authorization Guide for API key setup.
Custom Pricing
When use-custom-pricing is enabled on the embed or template, Mimeeq's pricing engine is completely bypassed — no internal price fetch occurs. The mimeeq-price-change event fires on every option or quantity change specifically so your code knows when to recalculate and call setPrice().
If setPrice() is never called after enabling custom pricing, the user sees "-" (same as price: 0). There is no built-in loading state — you should show your own while the price is being calculated.
For modular products with custom pricing, only the total price can be set. The pricing.modularPrices and pricing.totalPriceModular observers are unavailable in this mode.
See Custom Pricing Integration for the full implementation guide.
Prices in Event Payloads
Events like mimeeq-add-to-cart and mimeeq-price-change include price fields in their payloads. These values reflect whatever is currently displayed in the configurator — either Mimeeq's calculated price or the value last set via setPrice().
Do not rely on event payload prices for order processing or server-side calculations. These values are easily tampered with client-side. Always re-fetch the authoritative price from /get-product-price-info on your server when processing an order.
Choosing Your Integration Approach
| Scenario | Approach |
|---|---|
| Standard B2C — single public price for all visitors | Built-in pricing with a Public Price List Group on the embed template. No code required. |
| B2B with company-specific pricing using Mimeeq auth | Built-in pricing — Tier 2 login automatically switches to company price list. |
| B2B with your own auth system | Fetch price via /get-product-price-info using companyId, then call setPrice(). Enable use-custom-pricing. |
| Show different prices for different user segments | Fetch via priceListIds per segment, call setPrice(). Enable use-custom-pricing. |
| Pull prices from an external ERP or pricing service | Call external API on mimeeq-price-change, call setPrice() with the result. Enable use-custom-pricing. |
| Display prices before rendering the configurator | Fetch via REST API using templateId or priceListGroup during page load. |
| Compare prices / promotional display | Configure Standard + Promo price lists in the same Public Price List Group. Built-in — no code required. |