B2B / Customer Groups
Group-Specific Fields
Makaira supports customer group-specific field overrides for B2B scenarios, multi-channel setups, and personalized pricing.
Overview
Any field can have a group-specific override using the pattern:
groups.<groupname>.<fieldname>
When a GROUP constraint is set in the API request (query.group: "<groupname>"), Makaira applies the group-specific values. Group names are case-sensitive and must match the constraint value exactly.
How It Works
Group overrides behave differently for visibility fields than for value fields. This is the most important detail of the feature — please read carefully before designing your group strategy.
Value fields (price, custom fields, ...) — full override
For non-visibility fields, the group value fully replaces the root value when the matching GROUP constraint is set.
Root: price = 99.99
Group "wholesale": price = 79.99
→ With GROUP=wholesale → returned price is 79.99
→ Without GROUP constraint → returned price is 99.99
Visibility fields (active, searchable, hideOnLists, onstock) — gate, not toggle
active, searchable, hideOnLists, onstock) — gate, not toggleVisibility fields work differently. The root field is always evaluated as a hard gate, and the group override can only further restrict visibility — it can never unlock a product that is hidden at the root level.
| Root | Group override | Result for that group |
|---|---|---|
active: true | not set | visible |
active: true | active: false | hidden |
active: false | not set | hidden |
active: false | active: true | still hidden |
active: false | active: false | hidden |
The same logic applies to searchable, hideOnLists, and onstock.
"Hidden by default, visible per group" is not supportedA common request is "make products inactive by default, then enable them for specific customer groups". This does not work with
active,searchable,hideOnLists, oronstock. The root field has to be in the permissive state (active: true,searchable: true,onstock: true,hideOnLists: false) for any group to ever see the product.If you need that semantic, model it the inverse way: keep the root permissive, and explicitly set the group override to
falsefor every group that should not see the product. Or use a custom keyword field plus a stream filter to control inclusion outside the visibility-field path.
Commonly Overridden Fields
| Field Pattern | Description |
|---|---|
groups.<group>.active | Group-specific activation |
groups.<group>.hidden | Group-specific visibility |
groups.<group>.hideOnLists | Group-specific list visibility |
groups.<group>.searchable | Group-specific searchability |
groups.<group>.onstock | Group-specific stock status |
groups.<group>.stock | Group-specific stock quantity |
groups.<group>.price | Group-specific pricing |
Import Example
{
"id": "product-001",
"type": "product",
"active": true,
"onstock": true,
"price": 99.99,
"stock": 100,
"groups": {
"wholesale": {
"active": true,
"onstock": true,
"price": 79.99,
"stock": 1000,
"hideOnLists": false
},
"retail": {
"active": true,
"onstock": true,
"price": 99.99,
"stock": 50
},
"vip": {
"active": true,
"price": 69.99,
"hideOnLists": false
},
"internal": {
"active": false
}
}
}API Request with Group
To use group-specific values, include the GROUP constraint:
{
"searchPhrase": "t-shirt",
"isSearch": true,
"constraints": {
"query.shop_id": "shop1",
"query.language": "de",
"query.group": "wholesale"
}
}Use Cases
B2B Pricing
Different prices for different customer tiers:
{
"id": "product-001",
"price": 99.99,
"groups": {
"wholesale": {"price": 79.99},
"distributor": {"price": 59.99},
"retail": {"price": 99.99}
}
}Group-Specific Visibility
Hide products from certain groups:
{
"id": "product-001",
"active": true,
"groups": {
"consumer": {"active": true},
"b2b_only": {"active": true},
"internal": {"active": false}
}
}Group-Specific Stock
Different stock pools per channel:
{
"id": "product-001",
"stock": 100,
"onstock": true,
"groups": {
"online": {"stock": 50, "onstock": true},
"store_pickup": {"stock": 25, "onstock": true},
"wholesale": {"stock": 500, "onstock": true}
}
}Regional Availability
Products available only in certain regions:
{
"id": "product-001",
"active": true,
"groups": {
"de": {"active": true},
"at": {"active": true},
"ch": {"active": false}
}
}Visibility Filter Logic
This section is the precise rule behind the gate-not-toggle callout above.
When a group is set, the search query for visibility fields is built like this (pseudo-code, see ActiveFilter, SearchableFilter, HideOnListsFilter, OnstockFilter in the API code):
must: root field is in the permissive state # always required
must_not: groups.<group>.<field> is in the restrictive state # only added when GROUP is set
For active, searchable, onstock the permissive state is true; for hideOnLists it is false. The group override can only add an additional must_not to hide the product for that group — it cannot remove the root must.
Logic for active field:
- Root
activemust betrue— otherwise the product is hidden for everyone, regardless of group. - If
GROUPis set andgroups.<group>.activeisfalse, the product is additionally hidden for that group. - If
GROUPis set andgroups.<group>.activeistrueor missing, the product follows the root value.
Examples:
Root: active = true, Group "wholesale": active = false
→ Product is HIDDEN for wholesale customers, visible for everyone else.
Root: active = false, Group "wholesale": active = true
→ Product is HIDDEN for wholesale customers too — root gate wins.
Stock Ranking with Groups
When "Out of stock at the end" is enabled in Ranking Mix:
- Makaira checks for
groups.<group>.stock - If exists, uses group-specific stock for ranking
- If not, falls back to root
stockfield
{
"id": "product-001",
"stock": 100,
"groups": {
"wholesale": {"stock": 0}
}
}For wholesale customers, this product would be pushed to the bottom of results (stock = 0).
Custom Group Fields
You can add any custom field as a group override:
{
"id": "product-001",
"delivery_days_int": 3,
"min_order_quantity_int": 1,
"groups": {
"wholesale": {
"delivery_days_int": 1,
"min_order_quantity_int": 10
},
"dropship": {
"delivery_days_int": 5,
"min_order_quantity_int": 1
}
}
}Best Practices
-
Define all groups consistently - Use the same group names across all products.
-
Set defaults at root level - Always provide root field values as fallback.
-
Only override what changes - Don't duplicate values that are the same as root.
-
Document your groups - Maintain a list of group names and their purposes.
-
Test group visibility - Verify that group-specific filtering works as expected.
Limitations
- Visibility fields are gated by the root, not toggled.
active,searchable,hideOnLists,onstockcannot be unlocked by a group override — see Visibility fields: gate, not toggle. - Group overrides are only applied when the
GROUPconstraint is set. Withoutquery.groupin the request, every consumer sees the root values. - Group names are case-sensitive and must match the constraint value exactly.
- Group fields must keep the same type as the root field. A
priceoverride has to stay numeric, astringoverride has to stay a string. - The
groupsobject must exist on the product and on every variant of that product, with consistent sub-fields across all groups (see the NDJSON guide). Inconsistent sub-fields lead to broken Elasticsearch mappings. - Nested attribute structures (
attributeStr,attributeInt,attributeFloat,attributes) cannot be overridden viagroups. Group overrides only apply to flat fields. - No partial merging. A group override replaces the root value as a whole — there is no per-sub-field merge for object-typed fields.
Updated 19 days ago
