Product Model

Overview

A Product is the core sellable entity in the platform. Products are typed via product_type, which fundamentally changes their role and behavior. All products share the same Product table and class — child and grouped products are not separate entity types.

Products support soft deletion: is_deleted (bool) and deleted_at (DateTime?) are set when a product is deleted rather than removing the row. Regular users cannot query deleted products at all; admin users can retrieve them by passing is_deleted=true as a query parameter.


Product Types

product_type Value Description
normal 0 Standard standalone product
grouped 3 A parent product that presents selectable child products via variant groups
child 4 A sub-product used as the backing product for a variant option; not sold independently
fee 5 A fee automatically added to an order; not a purchasable item
mix_and_match_bundle 1 A bundle the customer assembles themselves from a pool of products
predefined_bundle 2 A bundle with a fixed set of included products

Normal Products

The default type. Has its own prices, localizations, images, variants, and categories. Appears in catalog listings unless hidden. Everything described in the rest of this document applies to normal products as the baseline.


Child Products (product_type = child)

Child products are sub-products used as the backing for variant options — most commonly inside grouped products. They are not directly purchasable from the catalog.


Grouped Products (product_type = grouped)

A grouped or grouping product is a parent product whose purchasable content is defined via variant groups. The customer selects which child products to buy by choosing from variant options.


Localizations (Multilingual Content)

Text content lives in localizations (IList<IProductLocalization>), one entry per language. Resolution falls back to a default language if the user's language has no entry.

Each localization record contains:

Field Notes
title Full product name
short_title Compact name used in cart and receipts
short_description HTML rich text; shown in listings
long_description HTML rich text; shown on product detail page
highlight_text Promotional callout text
eye_catcher_text Attention-grabbing badge/label
checkout_text Additional text shown during checkout
receipt_text Appears on customer order confirmation
merchant_receipt_text Appears on merchant-side receipts
unit_title Custom unit label (e.g. "package", "box")
keywords List<string> for search indexing
details_url External URL to product page in another system

Title resolution priority in code: short_titletitlecomputed_title#<id>.

Description resolution: short_description (when preferShorter = true) → highlight_text fallback; or long_description for full detail.


Prices

prices is an IList<EntityPrice> — each product can have multiple price entries.

Key pricing rule: When multiple prices are present, the lowest value is the sale price (what the customer pays) and the next-lowest is the regular/original price (shown as a crossed-out "was" price).

Each EntityPrice has:

Field Notes
value Price amount
currency_id / currency Currency for this price
kind each (per unit), total (flat), per_unit (by measurement)
min_amount Minimum quantity this price applies to (default: 1)
campaign_id If set, price is only active during that campaign
price_group_id Price group for segment-based pricing (e.g. wholesale)
client_account_id Account-specific price override
title Internal name for the price entry

computed_active_prices (not persisted) reflects the currently applicable prices for the requesting user/context after campaign, account, and price group resolution. See Computed Fields for how to request it.


Categories

category_relations is an IList<ProductRelCategory> — a product can belong to multiple categories.

Each ProductRelCategory record:

Field Notes
product_id The product
category_id The category
is_primary_category Marks the main category (used for breadcrumbs, computed_main_category_id)
ordinal Sort position within this specific category
campaign_id Category membership is campaign-scoped (optional)

computed_main_category_id and computed_main_category_title are derived from the is_primary_category = true relation.

Products also have a top-level ordinal field for global ordering outside of a category context. When ordering within a category, product_rel_category.ordinal takes precedence over the product-level ordinal.


Visibility & Access Control

Field Behavior
is_active false= product is completely inactive; not shown or purchasable
is_hidden true= hidden from catalog listings but accessible via direct link
is_campaign_only Only purchasable when an active campaign includes it
is_purchasing_disabled Product is visible but cannot be added to cart
is_restricted_to_specific_client_accounts Only shown to explicitly granted customer accounts
is_restricted_to_providers Only shown to logged-in sellers/admins

Variants

Variants allow a single product to have configurable options (e.g. size, color, or — for grouped products — which child products to include). The system has several interconnected layers.

VariantGroup (shared definition)

VariantGroup is a reusable shared entity. A product links to variant groups via variant_group_relations (IList<ProductVariantGroup>).

Key fields on VariantGroup:

Field Notes
group_kind Controls how selections count — see VariantKind below
picker_kind UI control used to display this group — see VariantPickerKind below
is_product_variants When true (default), each variant option is backed by a Product. When false, variants are localization-only labels with no backing product; pricing comes from VariantProperty.price directly.
is_amount_pickable Customer can specify a quantity per option
min_amount / max_amount Min/max total quantity across all options in the group
min_number_of_choices / max_number_of_choices Min/max number of distinct options the customer must/can select (independent of individual quantities)
prices Default prices for this group; can be overridden at the product level
properties The variant options (IList<VariantProperty>)

VariantKind (group_kind)

Value Description
ingredient Option is a component of the product. Quantities multiply with the parent product amount when processed in orders.
variation Option is a product variation. In grouped products, selections from variation groups are what counts toward the grouped product's quantity sum.
acquisition Add-on purchase. Counted and priced independently — quantities do not multiply with the parent product amount.

VariantPickerKind (picker_kind)

Value Description
toggle_box Checkbox (default)
button Button selector
toggle_box_button Toggle button
dropdown Dropdown — implicitly single-choice
amount_only Quantity picker only, no label
file Image/file upload — implicitly single-choice
text Free-text input — implicitly single-choice

dropdown, file, and text pickers enforce a maximum of one selection.

ProductVariantGroup (product-specific overrides)

Each entry in product.variant_group_relations is a ProductVariantGroup — a product-specific configuration that overrides the shared VariantGroup settings for this product only.

Field Notes
variant_group_id / variant_group The shared group definition
is_locked Prevents the customer from changing selections (staff can still override)
free_amount Number of units from this group that are included at no charge
min_amount / max_amount Overrides the group's total quantity limits for this product
free_number_of_choices Number of distinct choices that are free (no charge)
min_number_of_choices / max_number_of_choices Overrides the group's choice count limits for this product
prices Product-specific prices for this group
product_variant_properties Product-specific settings per variant option (IList<ProductVariantProperty>)

Effective limits resolve as: ProductVariantGroup value if set → VariantGroup value → special default (e.g. min=1 when it is a grouped product with a single variant group).

VariantProperty (variant option)

Each option within a group. When VariantGroup.is_product_variants = true, each VariantProperty has a product reference to its backing Product (typically product_type = child).

Title resolution: computed_titleproduct.title→ localization name.

Field Notes
product_id / product The backing product for this variant option
price Default price surcharge for this variant (used when no product-level override exists)
is_default Pre-selected by default
amount_per_product Required quantity of this variant per unit of parent product ordered
min_amount / max_amount Quantity limits for this option

ProductVariantProperty (per-product variant override)

Each entry in product_variant_group.product_variant_properties is a ProductVariantProperty — a product-specific override for a single variant option within a ProductVariantGroup.

Field Notes
price Overrides the variant's default price when purchased as part of this specific parent product
is_default Pre-selection override for this product
is_locked Customer cannot change this variant on this product
amount_per_product Quantity override
min_amount / max_amount Quantity limit overrides

Pricing for product-backed variants

When a variant is backed by a product (VariantProperty.product), the variant's price is taken from that product's prices. ProductVariantProperty.price overrides this price specifically for that variant when it is purchased through a particular parent product — allowing the same child product to carry a different price depending on which parent it is bought through.

VariantCombinations

variant_combinations on the product pre-defines valid or forced combinations of variant choices (e.g. only certain size+colour pairings are available). Used to restrict or preset selections. Categories can also carry their own variant_combinations; these are merged with the product's combinations (computed_variant_combinations on the category) to control which variants are visible when browsing that category.


Bundles


Stock

stock_relations (IList<IProductStock>) — one record per inventory location.

Field Notes
is_available Manual on/off switch
is_stock_managed Whether quantity is tracked
amount_in_stock Current quantity
allow_order_out_of_stock Allow ordering even when depleted
campaign_id Stock record is campaign-scoped (optional)

Ordering Constraints

Field Notes
min_amount_per_order Minimum quantity customer must order
max_amount_per_order Maximum quantity customer can order
is_amount_locked Customer cannot choose quantity; it is assigned by rules
order_periods Time windows during which the product is purchasable
delivery_periods Time windows for delivery/handover
min_customer_age Age-gated product
required_customer_type Restrict to a customer kind (e.g. business, individual)

Shipping

Field Notes
is_digital No physical shipment
shipping_price Fixed shipping cost override
is_shipping_price_per_order Fixed cost regardless of quantity
weight kg
length / width / height Meters
is_packaged_separately Must ship alone, not combined
compatible_shipping_method_relations Allowed shipping methods

Manufacturing

Field Notes
number_of_preparation_days Days needed before the product is ready
preparation_start_latest_time_of_day Latest time of day to start preparation
preparation_off_days_of_week Days when no preparation happens
manufacture_duration Time to produce one unit (multiplies with quantity ordered)

Additional Purchase Effects

Purchasing a product can trigger side-effects:

Field Notes
product_granting_ownable_items Adds items to the user's inventory
product_granting_permissions Grants platform permissions
product_granting_subscriptions Creates or extends a subscription plan
product_granting_rebates Grants discount codes
product_granting_tokens Grants redemption tokens from a pool
is_granting_sub_order_token Generates a sub-order token after purchase

Vouchers / Tickets

Field Notes
is_redeemable Generates a voucher/ticket on purchase
is_single_ticket_preferred Bundle multiple vouchers into one
is_ticket_sent_before_payment Send voucher before payment is confirmed
is_user_redeemable Voucher can be redeemed without a QR code

Computed Fields (read-only, resolved at query time)

Computed fields are marked IncludeByDefault=false and are not returned in responses unless explicitly requested. Use the include query parameter to request them:

GET /resources/Product/123?include=computed_active_prices,computed_main_category_id

Multiple fields can be comma-separated. See RestApi-en.md for full documentation on include.

Field Notes
computed_active_prices Prices effective for the current user/campaign context
computed_main_category_id / computed_main_category_title Primary category
computed_is_new Whether the product is flagged as new
computed_price_reductions Discount percentages per price group
computed_number_of_sold Sales count (provider-only)
computed_title Resolved title (falls back across localization)
computed_owner_department_title Owner department name