# GroupPrice

> Per-customer-group price overrides for a single price field, serialised into one column.

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/technical/property-types/groupprice. Last updated 2026-07-27.

---
Per-customer-group price overrides for a single price field, serialised into one column.

**Storage kind:** `column` · **Column:** `text`

Use it to let a shop owner say "this price, but different for these customer groups". It always
accompanies a price property rather than standing alone — `overridePropertyName` names the field it
overrides, and the form borrows that field's label so the two read as one control.

## Settings

| Setting | Meaning |
|---|---|
| `overridePropertyName` | The price property this overrides. Its label is borrowed for display |
| `unit` | Unit shown alongside the values |

## The stored value

A JSON array of `{group_id, price}` entries, posted from a **hidden input** that the property's own
JS maintains:

```json
[{"group_id": 3, "price": "89.00"}, {"group_id": 7, "price": "72.50"}]
```

`group_id` is cast to `int` and `price` is normalised from the localised decimal mark to a dot on the
way in, so the stored text is canonical regardless of the operator's locale.

Reading is forgiving in one specific way: `getOverrides()` **drops entries whose customer group no
longer exists** rather than showing a row with no name. The stale entries stay in the column until
the next save, which then writes them out. So the column can legitimately contain groups that are
gone, and a consumer reading it directly has to tolerate that — do not assume every `group_id`
resolves.

The list cell shows a count ("2 overrides"), not the values.

## Notes

- It was `varchar(1024)` and is now `text`. The blob outgrew the column, and a fixed width on a
  serialised list is a limit nobody can predict from the form.
- **A malformed value used to be silently discarded.** `json_decode()` answers `null` for anything
  that is not valid JSON, and iterating `null` only raised a warning — so a failed edit, an older
  browser or a custom template could make the whole set store as empty, losing overrides a shop
  owner had entered with nothing to see. A non-array is now treated as empty explicitly.
- There is no `check()` override: the property does not validate the set beyond the coercion above.
  An override for a group that does not exist is accepted and simply ignored on read.
- Because the whole set lives in one column, there is no way to query "which products have an
  override for group 7" in SQL without parsing JSON in the query. That is the trade this shape makes.

@see calculationoverride.md — the same shape for calculations
@see number.md — the price property being overridden
@see ../../admin-guide/commerce/set-up-customer-groups.md — the groups these override for
@see ../../admin-guide/pricing/where-prices-come-from.md — where an override lands in the price chain
