# The customer field rename — before → after reference

> The exhaustive reference for the CBX 4 rename of the customer record's fields (#configboxusers and the frozen per-order snapshot #cbcheckoutorderusers). Read…

Source: CBX documentation, version 4.0 preview (unreleased). Canonical page: https://docs.configbox.at/docs/4.0-preview/migration-to-cb4/customer-field-rename. Last updated 2026-08-01.

---
**The exhaustive reference for the CBX 4 rename of the customer record's fields** (`#__configbox_users`
and the frozen per-order snapshot `#__cbcheckout_order_users`). Read this for any customization that touches
customer data — the customer model, the customerform (checkout/quotation/profile/save-order), order
addresses, invoice/notification placeholders, PSP bridges, or raw SQL against either table.

> This is the sibling of [element-question-rename.md](https://docs.configbox.at/docs/4.0-preview/migration-to-cb4/element-question-rename). Same playbook shape:
> the full before → after tables, then the per-file decision procedure.

---

## Why this change

The customer record carried **three naming families at once**:

1. **Glued-together `billing*` columns** — `billingfirstname`, `billingcompanyname`, `billingzipcode` …
2. **UN-prefixed columns that actually meant the shipping address** — `firstname`, `city`, `country` …
   (labelled "Delivery" in the UI; the bare name looked like the primary field and was not)
3. **Odd ones** — `vatin`, `samedelivery`, references without `_id` (`country`, `state` held ids)

CBX 4 renames all of it to one convention, aligned with the commerce2 (`#__cbx_*`) vocabulary:
**snake_case, `billing_` / `shipping_` prefixes, `_id` suffix on references, `postal_code`,
`address_line_1/2`, `vat_number`.** The un-prefixed set is now explicitly **`shipping_*`** — the
counterintuitive "bare name = shipping" trap is gone.

**The mental-model swap:** wherever code said `billingfirstname` it now says `billing_first_name`;
wherever it said a bare `firstname`/`city`/`country` *about a customer or order address*, it now says
`shipping_first_name`/`shipping_city`/`shipping_country_id`. Record **ids are unchanged** — customers,
orders and their links all keep pointing at the same rows. Only names changed.

**Not touched** (do not "fix" these — they are not customer fields):

- The geo tables' own columns: `#__configbox_cities.county_id`, `#__configbox_counties.state_id`, … and
  the tax-rate table's `county_id`/`city_id` columns.
- The cascade AJAX request params of the customerform controller: `country_id`, `state_id`, `county_id`.
- The registration form's POST field names (`firstname`, `lastname`, `email` in
  `views/user/tmpl/register.php`) — form-local names, mapped to `billing_*` in the controller.
- `custom_1..4`, `group_id`, `platform_user_id`, `language_tag`, `newsletter`, `is_temporary`, `created`,
  `password`, `id`, `order_id` — already fine, unchanged.
- Presentational CSS classes inside invoice/quotation/order-slip templates (`class="firstname"`,
  `class="zipcode"` …) — DOM styling hooks, not field keys.
- The commerce2 schema (`#__cbx_party`, `#__cbx_address`, …) — it already used the target vocabulary.

---

## The DB migration ships with the core

**`helpers/updates/3.7.0.php`** (runs on init and via `php cli/joomla.php configbox:migrate`) does four
things, idempotently:

1. Renames every column below on **both** `#__configbox_users` and `#__cbcheckout_order_users`
   (precedent: the element→question rename also renamed frozen order history). Indexes named after the
   old columns are renamed alongside. Data, ids and FKs are untouched.
2. **Drops `gender` and `billinggender`** on both tables (dead since 2.6.0 replaced them with
   `salutation_id`; see "Removed" below).
3. Rewrites the **`#__configbox_user_field_definitions.field_name` rows** (the Customer Fields
   configuration) to the new names — keyed on `field_name`, since row ids are not stable.
4. Rewrites **`{placeholder}` tokens in stored `#__configbox_strings` texts** (notification
   subjects/bodies) from the old vocabulary to the new — including the legacy alias tokens
   (`{country}` → `{shipping_country_name}` etc., see the placeholder table).

**It does not touch `data/customization/`** — custom SQL, custom columns and custom code are yours to
realign using the tables below.

---

## Before → after — DB columns

Applied identically to `#__configbox_users` **and** `#__cbcheckout_order_users` (per-table column types
are preserved). Status for customization code: 🔴 manual — there are **no shims**; raw SQL, model
overrides and template overrides referencing old names break until edited.

### Billing address

| Before | After |
|---|---|
| `billingcompanyname` | `billing_company_name` |
| `billingsalutation_id` | `billing_salutation_id` |
| `billingfirstname` | `billing_first_name` |
| `billinglastname` | `billing_last_name` |
| `billingaddress1` | `billing_address_line_1` |
| `billingaddress2` | `billing_address_line_2` |
| `billingzipcode` | `billing_postal_code` |
| `billingcity` | `billing_city` |
| `billingcountry` | `billing_country_id` |
| `billingstate` | `billing_state_id` |
| `billingcounty_id` | `billing_county_id` |
| `billingcity_id` | `billing_city_id` |
| `billingemail` | `billing_email` |
| `billingphone` | `billing_phone` |

### Shipping address (was the UN-prefixed set, "Delivery" in the UI)

| Before | After |
|---|---|
| `companyname` | `shipping_company_name` |
| `salutation_id` | `shipping_salutation_id` |
| `firstname` | `shipping_first_name` |
| `lastname` | `shipping_last_name` |
| `address1` | `shipping_address_line_1` |
| `address2` | `shipping_address_line_2` |
| `zipcode` | `shipping_postal_code` |
| `city` | `shipping_city` |
| `country` | `shipping_country_id` |
| `state` | `shipping_state_id` |
| `county_id` | `shipping_county_id` |
| `city_id` | `shipping_city_id` |
| `email` | `shipping_email` |
| `phone` | `shipping_phone` |

### Other

| Before | After | Note |
|---|---|---|
| `vatin` | `vat_number` | |
| `samedelivery` | `shipping_same_as_billing` | Semantics unchanged: `1` = billing gets copied over shipping |
| `gender`, `billinggender` | ⛔ **removed** | See "Removed" below |

---

## Before → after — computed (augmented) keys

`ConfigboxUserHelper::augmentUserRecord()` adds derived keys to every user/order-address record
(`ConfigboxUserData`). Templates and placeholders use them heavily. 🔴 manual.

| Before | After |
|---|---|
| `countryname` / `country_2_code` / `country_3_code` | `shipping_country_name` / `shipping_country_2_code` / `shipping_country_3_code` |
| `billingcountryname` / `billingcountry_2_code` / `billingcountry_3_code` | `billing_country_name` / `billing_country_2_code` / `billing_country_3_code` |
| `statename` / `statecode` / `statefips` | `shipping_state_name` / `shipping_state_code` / `shipping_state_fips` |
| `billingstatename` / `billingstatecode` / `billingstatefips` | `billing_state_name` / `billing_state_code` / `billing_state_fips` |
| `county` / `billingcounty` | `shipping_county` / `billing_county` |
| `salutation` / `billingsalutation` | `shipping_salutation` / `billing_salutation` |
| `gender` / `billinggender` (augmented) | `shipping_gender` / `billing_gender` — now **augment-only**, derived from the salutation (`configbox_salutations.gender`); the DB columns are gone |

`language_name` and `order_id` are unchanged.

---

## Before → after — template placeholders (operator-facing) 🟠

Invoice and email-notification texts substitute `{key}` tokens generated from the order-address record,
so the rename **changes the placeholder vocabulary operators typed into their own templates**. The core
migration **rewrites stored texts** in `#__configbox_strings` (notification subjects/bodies, all
languages) — that is the 🟠 part: *the data moved; anything of yours that writes or documents these
tokens did not.*

Every column and augmented key above maps 1:1 (`{billingfirstname}` → `{billing_first_name}`, …).
Additionally, four **legacy alias tokens** resolved to the *name* of a place while sharing the raw
column's key — they map to the `*_name` tokens, and the explicit alias lines in `models/invoice.php`
are gone (the generic record loop covers the new names):

| Before | After |
|---|---|
| `{country}`, `{countryname}` | `{shipping_country_name}` |
| `{billingcountry}`, `{billingcountryname}` | `{billing_country_name}` |
| `{state}`, `{statename}` | `{shipping_state_name}` |
| `{billingstate}`, `{billingstatename}` | `{billing_state_name}` |
| `{salutation}` | `{shipping_salutation}` |
| `{billingsalutation}` | `{billing_salutation}` |

The gendered-variation syntax in notifications (`{Sehr geehrter|Sehr geehrte}`) is **unchanged** — it
keys off the (now augment-derived) `billing_gender`.

---

## Before → after — the Customer Fields configuration (data!)

`#__configbox_user_field_definitions.field_name` holds these names as **row values**; the migration
rewrites all 30 rows. If your customization queries this table, or your code calls
`ConfigboxUserHelper::getUserFields()` / `getUserFieldTranslations()` and branches on field names,
apply the same column tables above. The two synthesized runtime-only keys are now
`shipping_city_id` / `billing_city_id` (cloned from `shipping_city` / `billing_city` rows).

---

## Before → after — HTTP, DOM and JS contracts

The customerform posts **field names as input names**, and validation issues come back keyed by field
name. All of it follows the rename. 🔴 manual for template overrides and custom JS.

| Contract | Before | After |
|---|---|---|
| Form input `name`/`id` attributes | `billingfirstname`, `firstname`, `samedelivery`, `vatin`, … | `billing_first_name`, `shipping_first_name`, `shipping_same_as_billing`, `vat_number`, … |
| Field wrapper CSS classes | `.customer-field-billingcity`, `.customer-field-city` | `.customer-field-billing_city`, `.customer-field-shipping_city` |
| Admin Customer Fields rows | `.cb-userfield-billingemail` | `.cb-userfield-billing_email` |
| `getCustomerFormData()` payload keys | old names | new names |
| Validation issue `fieldName` values | old names | new names |
| Admin orders list sort keys | `a.billinglastname`, `a.lastname` | `a.billing_last_name`, `a.shipping_last_name` |

**Prefix arithmetic is gone.** The billing→shipping pairing used to be
`substr($fieldName, 7)` / `'billing' + field_name` in three places. It is now an explicit prefix map —
if your code paired fields the old way, mirror this:

```php
// PHP (ConfigboxModelCustomers::prepareForStorage)
if (strpos($fieldName, 'billing_') === 0) {
    $pendant = 'shipping_'.substr($fieldName, strlen('billing_'));
}
```

```js
// JS (customerform.js)
if (fieldName.indexOf('billing_') === 0) {
    let pendant = fieldName.replace(/^billing_/, 'shipping_');
}
```

---

## Removed ⛔

- **`gender` / `billinggender` columns** (both tables). Dead storage since 2.6.0 introduced
  `salutation_id` — nothing could write anything but the column default. The *behaviour* survives:
  `augmentUserRecord()` derives `shipping_gender` / `billing_gender` from the selected salutation's
  `gender` column at read time, so notification gender variations keep working. Code that **selected**
  the columns in SQL must switch to the augmented keys (or join `#__configbox_salutations`).
- **Dead validation arms** for `county` / `billingcounty` in `ConfigboxModelCustomers::validateData()`
  (unreachable since 2.6.14 renamed the rows to `*county_id`).
- **`disabledValidation` list** in `adminUserFields.js` — targeted inputs the admin table stopped
  rendering long ago.

## Behaviour fixes shipped with the rename

Latent bugs in the code being renamed, fixed in the same commit (behaviour changes, not renames):

1. **Checkout city snapshot**: `setOrderAddress()` gated the billing-city geo snapshot on
   `billingcounty_id` and compared the shipping city against `billingcounty_id` (copy-paste bug) — city
   data could be missing from order snapshots.
2. **Billing country fallback**: `augmentUserRecord()`'s non-order path resolved the billing country
   from the *shipping* country id.
3. **Tax lookup**: `ConfigboxPricesHelper::getTaxRate()` passed `$user->county` (the county *name*,
   `intval` 0) where the county id belongs — county-level tax rules never matched through this path.
4. **Email salutation**: the invoice/registration emails greeted with the *shipping* salutation next to
   the billing name; they now use `billing_salutation`.

---

## Per-file decision procedure (for customization code)

For every file in `data/customization/` (and any template/model override):

1. **Grep for the old tokens** — the CHEATSHEET grep pack covers them. The glued names
   (`billingfirstname`, `vatin`, `samedelivery`, …) are unambiguous: rename on sight.
2. **Bare `firstname`/`city`/`country`/`state`/`county_id`/`city_id`/`email`/`phone`/`salutation_id`
   need a judgement call**: about a customer, an order address or the customerform → `shipping_*`.
   About the geo tables, the tax tables, a platform user, or an unrelated form → leave alone.
3. **Raw SQL** against `#__configbox_users` / `#__cbcheckout_order_users`: apply the column tables.
   `SELECT *` consumers break at the *reading* site, not the query — check what property names the
   result is used with.
4. **Stored texts your customization writes** (notification bodies, invoice fragments): emit the new
   placeholder vocabulary. The core migration converted existing rows; your writers must not
   re-introduce old tokens.
5. **Custom columns you added to either table are untouched** — but if your column names collide with
   the new vocabulary (e.g. you added your own `billing_email`), resolve that *before* running the
   migration; the rename is guarded and will skip a rename whose target name already exists, leaving
   the old column in place.
6. After editing: `php cli/joomla.php configbox:cache:clear`, then walk checkout once with
   `shipping_same_as_billing` checked and once unchecked, and render one invoice + one notification.
