---
url: /process-functions/cube-crosstab.md
description: A grid of cube data, read row by row, with several values on each row.
---

# &#x20;Crosstab

A grid of cube data, read row by row, with several values on each row.

```js
const table = cube.crosstab("Sales", {
    rows:    { "Product": [{ set: ["All Products"] }, "expand-all", "remove-all-consolidations"] },
    columns: { "Measures": [{ set: ["Units", "Revenue"] }] },
    context: { "Year": "2026", "Version": "Actual" }
});

for (const row of table) {
    console.log(row.label + ": " + row.values.join(", "));
}
```

## Constructor

### cube.crosstab `cube.crosstab(cubeName, options)`  {#cube-crosstab}

Reads a grid of values from a cube. Returns `null` when the cube, a dimension or an element can't be found, or the options aren't valid. The reason is written to the process log.

| Parameter | Type | Description |
|---|---|---|
| `cubeName` | `string` | The cube's name or id. |
| `options` | `object` | Which elements go on the rows and columns; see the table below. |

| Option | Type | Description |
|---|---|---|
| `rows` | `object` | The dimensions on the rows. Each key is a dimension's name and each value is an array of [set instructions](/set-instructions/in-scripts). At least one is required; two give a row for each combination. |
| `columns` | `object` | The same, for the columns. At least one is required. |
| `context` | `object` | One element for each remaining dimension, keyed by dimension name. |
| `suppress` | `string` | Optional. `"rows"`, the default, leaves out rows with no values. `"none"` keeps every row. |
| `security` | `boolean \| string` | Optional. `false`, the default, reads everything, ignoring security. `true` reads as the signed-in user, and a user's id or email address reads as that user. |
| `maxRows` | `number` | Optional. The most rows to read. |

Every dimension of the cube must be on the rows, the columns or in `context`.

## Overview

* **Set instructions:** each dimension on an axis takes an array of instructions, run in order, the same as a Workview set. Use `{ set: ["All Products"] }` to select elements (add `hierarchy` to take them from a named hierarchy), and plain strings such as `"expand-all"` and `"remove-all-consolidations"` for actions.
* **When to use it:** use [cube.get](/process-functions/cube-get) for one cell and [cube.slice](/process-functions/cube-slice) for many leaf-level cells. Use `cube.crosstab` when rows follow a hierarchy, when a row needs several values, or when you need consolidated totals.
* **Rows:** each row has a `label`, its `members` (one per row dimension, with `dimension`, `name`, `hierarchy` and `consolidation`), and its `values`, in the same order as [columns()](#columns).
* **Reading twice:** rows are read as you loop, and each loop reads the cube again. Use [toArray()](#toarray) or [toGrid()](#togrid) if you need the rows more than once.
* **Limits:** an axis with more than 100,000 combinations is refused, and reading stops after 250,000 cells, with [isTruncated()](#istruncated) set.

## Methods

### toArray `toArray()`  {#toarray}

Reads every row and returns them as an array of row objects.

### toGrid `toGrid()`  {#togrid}

Reads every row and returns each one as a flat array: the row's element names, then its values, in the order [headers()](#headers) describes.

### headers `headers()`  {#headers}

Returns the row dimension names, then the column labels, matching a row from [toGrid()](#togrid).

### columns `columns()`  {#columns}

Returns the columns, in the same order as each row's `values`. Each column has a `label`, its `members`, its `measure`, its `elementType` (`"Numeric"` or `"String"`) and whether it's `consolidated`.

### warnings `warnings()`  {#warnings}

Returns messages about anything left out of the read, such as calculated elements that were dropped or the cell limit being reached.

### rowsProduced `rowsProduced()`  {#rowsproduced}

Returns how many rows have been read so far.

### cellsEvaluated `cellsEvaluated()`  {#cellsevaluated}

Returns how many cells have been read so far.

### isTruncated `isTruncated()`  {#istruncated}

Returns `true` when the 250,000-cell limit stopped the read early. Stopping at `maxRows` doesn't count.

## Examples

### Products with two measures

::: code-group

```js [products.js]
const table = cube.crosstab("Sales", {
    rows:    { "Product": [{ set: ["All Products"] }, "expand-all", "remove-all-consolidations"] },
    columns: { "Measures": [{ set: ["Units", "Revenue"] }] },
    context: { "Year": "2026", "Version": "Actual" }
});

for (const row of table) {
    console.log(row.label + ": " + row.values.join(", "));
}
```

```text [Output]
Widgets: 120, 1450.5
Gadgets: 85, 980
Gizmos: 40, 512.25
```

:::

### Regions with their totals

::: code-group

```js [regions.js]
const table = cube.crosstab("Sales", {
    rows:    { "Region": [{ set: ["All Regions"] }, "expand"] },
    columns: { "Measures": [{ set: ["Revenue"] }] },
    context: { "Year": "2026", "Version": "Actual" }
});

for (const row of table) {
    const total = row.members[0].consolidation ? " (total)" : "";
    console.log(row.label + total + " = " + row.values[0]);
}
```

```text [Output]
All Regions (total) = 9200
North = 5000
South = 4200
```

:::

### Export to CSV

::: code-group

```js [export.js]
const table = cube.crosstab("Sales", {
    rows:    { "Product": [{ set: ["All Products"] }, "expand-all", "remove-all-consolidations"] },
    columns: { "Measures": [{ set: ["Units", "Revenue"] }] },
    context: { "Year": "2026", "Version": "Actual" }
});

const writer = datasource.csv("exports/sales_2026.csv");
writer.write(table.headers());
for (const row of table.toGrid()) {
    writer.write(row);
}
writer.close();
```

```csv [sales_2026.csv]
"Product","Units","Revenue"
"Widgets",120,1450.5
"Gadgets",85,980
"Gizmos",40,512.25
```

:::

## Related

* [dimension.set](/process-functions/dimension-set): runs set instructions on a dimension without a cube.
* [Set Instructions in Scripts](/set-instructions/in-scripts): the instructions each axis takes.
