Skip to content

Dimensions and segments

How many ways can a company slice its profit and loss? 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.

Concepts
8
Divergences
3
Structural
2

Endpoints

Xero

GET /TrackingCategories

Categories with options

QBO

SELECT * FROM Class / Department

Two fixed entities

NetSuite

customsegment metadata + cseg_* fields

Field keys are discovered per account

The same concept in three vocabularies

Read across a row. Where a cell is absent, the concept has nowhere to live in that system — which is a design constraint, not a gap to paper over.

NativeNative
— A field or object exists for it
PartialPartial
— Exists with a caveat that changes what you can build
DerivedDerived
— Not stored — you compute it
AbsentAbsent
— Nothing in the API
Dimensions and segments fields compared across Xero, QuickBooks Online and NetSuite
ConceptXeroQBONetSuite
Mechanism
Partial

TrackingCategory with TrackingOption[]

Partial

Class and Department entities

Native

department, class, location, subsidiary + custom segments

How manyA hard product limit in Xero, not a plan limit.
Partial

Two, maximum

Active at a time (up to four defined), around 100 options each. A third active one is impossible

Partial

Two

Class and Department, each gated by a preference

Native

Four built-in plus unlimited custom

Naming
Native

User-named

"Region", "Team", "Project"

Partial

Fixed

The UI relabels Department as "Location"

Native

User-named custom segments

Hierarchy
Absent

Flat options

Partial

SubClass + ParentRef

Native

Hierarchical

Placement
Native

Line

Always

Partial

Class: line or header; Department: header

Depends on a company preference

Native

Header and line

Line overrides header

Constraints
Absent

—

Absent

—

Native

Required, filtered by subsidiary, script-validated

On journal lines
Native

Yes

Native

Yes

Native

Yes

DiscoveryWhether you can know the shape before you connect.
Native

Fetch the two categories

Native

Known in advance

Partial

Runtime — field keys are per account

cseg_product, cseg_channel…

What actually differs, and what you do about it

A difference stated without a response is trivia. Each of these carries the handling.

Structural

NetSuite dimension field names are not knowable in advance

NetSuite

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.

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

Structural

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

Xero

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.

Do — 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.

Sharp edge

Header dimensions and line dimensions are not the same fact

QBO

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.

Do — 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.

The type the components consume

Components take Dimension[] and render n of them. The number of dimensions is data, and zero is a valid answer.

dimensions.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 };

What this changes in the interface

The payoff. Every difference above that reaches the surface, and what the component does about it.

The dimension count is 0, 1, 2 or n
FilterBar and DimensionPicker map over a Dimension[]. There is no Class prop and no Department prop anywhere in the system.
Some dimensions are required on write
Required-ness comes from the dimension metadata, so a NetSuite mandatory segment blocks submit with a field-level message rather than a server error after the fact.
Xero caps at two
The dimension settings screen states the cap and the current usage, rather than offering an add control that will fail.

Where the accounting is explained

What consumes this payload

The normalised object above is what the components read. These are the surfaces it feeds, the words for the concepts in it, and the document that explains the accounting.

Compared acrossXero docsQuickBooks Online docsNetSuite docs

Works in your QuickBooks Online or Xero file

Get your books closed every month

Lopifi is building AI bookkeeping that keeps your books closed on US GAAP as transactions happen, with a CPA review every quarter, for $200 a month. We’re not open yet.

$200/month when we open. No card now.