# Dimensions and segments

> Two in Xero, named by the user. Two in QuickBooks, named by Intuit. Unlimited in NetSuite, named by the user and discovered at runtime. This is why a dimensional filter bar cannot be a static component.

Source: https://lopifi.com/apis/dimensions
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

**How many ways can a company slice its profit and loss?** Render dimensions from metadata. A component with hardcoded Class and Department columns is wrong on two of the three systems.

## Divergences between Xero, QuickBooks Online and NetSuite

### NetSuite dimension field names are not knowable in advance

A custom segment creates a field whose key is defined by the customer — cseg_product, cseg_channel, cseg_whatever. Two NetSuite accounts have different dimension schemas. No type literal in your code can name them.

**Handling:** Fetch the customsegment metadata at connection time, store it as a Dimension[], and drive every filter, group-by and column from that list.

### Xero's two-category limit is a wall, not a preference

A client who wants department and project and location on Xero cannot have all three. There is no plan, no setting and no workaround inside Xero's tracking model.

**Handling:** Say so, rather than offering a control that cannot be saved. A third dimension lives in our system, applied at the reporting layer and marked as ours.

### Header dimensions and line dimensions are not the same fact

QBO's Department is a header field. Fanning it down to lines at normalisation makes reporting uniform, but writing per-line values back is impossible — the write silently keeps only one.

**Handling:** Record appliesTo on each dimension. An edit UI shows a header control where the source stores it at the header, and a per-line control only where the source supports one.

## Canonical shape

```ts
type DimensionOption = {
  id: string;
  label: string;
  parentId?: string;      // Xero options are flat; QBO and NetSuite nest
  active: boolean;
};

type Dimension = {
  /** "tracking.0" | "class" | "department" | "cseg_product" — the source's key. */
  key: string;
  /** The user's own name for it. Never a label we invented. */
  label: string;
  options: DimensionOption[];
  appliesTo: "line" | "header" | "both";
  required: boolean;
  source: SourceRef;
};

type LineDimension = { key: string; optionId: string; label: string };
```
