# Reading worksheet data in an extension

> How a Tableau extension gets its data: props for a viz extension, the summary data reader for a dashboard, nativeValue vs formattedValue, and row caps.

Canonical: https://ext.tableauops.com/knowledge/reading-worksheet-data
Updated: 2026-09-18
Author: Eric Summers


The two extension kinds get their data in different ways, and confusing them is a common early mistake. A viz extension is handed its data. A dashboard extension goes and reads it. This page covers both paths, the value fields you read, and why you cap large reads.

## A viz extension is handed its data

A hosted viz extension component does not call any data method itself. The host resolves the worksheet's Marks card and passes the data in as props to your `Component` function:

```jsx
function Component({ data, schema, theme, fields }) {
  // data: array of plain row objects, may be empty
  // schema: [{ name, type }] describing the fields, may be undefined
  // fields: your Marks-card tile ids mapped to the field name dropped there
  // ...
}
```

Every value the component draws is there because the user dragged a field onto a tile you declared on the Marks card, and `fields` tells you which field landed where. Look columns up in `data` by that name, and always keep a schema-inference fallback so the same component still renders in a preview harness where there is no Marks card:

```jsx
const valueField = fields?.value || schema?.find(f => f.type === 'number')?.name;
```

If you are writing a raw (non-hosted) viz extension by hand instead, you call `worksheet.getVisualSpecificationAsync()` to learn which field is on which encoding tile, then read the rows with the summary data reader below. The hosted contract does that plumbing for you. See [viz extension encodings](/knowledge/viz-extension-encodings) for declaring the tiles.

## A dashboard extension reads through the reader

A dashboard extension has no props: it reaches into the dashboard and reads a worksheet itself. Get a worksheet, open a summary data reader, page through it, and release it:

```js
const dash = tableau.extensions.dashboardContent.dashboard;
const ws = dash.worksheets[0];
const reader = await ws.getSummaryDataReaderAsync();
const table = await reader.getAllPagesAsync();
await reader.releaseAsync();
```

`getSummaryDataReaderAsync` is the current path and it pages through large results. The older `getSummaryDataAsync` returns everything in one call and is fine for small data, but the reader is what keeps a big worksheet from blowing up memory. Always `releaseAsync()` when you are done.

## Find columns by name, read nativeValue

`getSummaryData` returns columns in pill order, which changes every time the user rearranges the sheet. Never index a column by position. Match it by field name (from the encodings) or by `dataType` (`'float'` / `'int'` for a measure):

```js
const cols = table.columns;
const mCol = cols.find(c => c.fieldName === 'Sales');
const rows = table.data.map(r => r[mCol.index].nativeValue);
```

Each cell has two values. `nativeValue` is the typed underlying value (a number, a Date), and it is what you do arithmetic and scales with. `formattedValue` is a display string with the worksheet's formatting baked in (currency symbols, thousands separators, rounding), and parsing it as a number quietly poisons your totals. Rule: `nativeValue` for maths, `formattedValue` for what you print.

## Cap large reads, and never truncate silently

Reading an unbounded worksheet is a way to freeze Tableau. Cap the read, and pick a cap for each distinct concern rather than one blanket number. The kit extensions use a layered pattern, for example the ridgeline mark:

| Cap | Value | Purpose |
|---|---|---|
| `READ_CAP` | 100,000 rows | Checked before the first page. Over it, the chart reads nothing and says so (filter, or use a coarser field). |
| `RIDGE_CAP` | 50 series | Legibility: the largest-total categories are kept. |
| `MAX_MARKS` | 4,000 points | The smallest-total series drop first. |

The point of separate caps is that "too much data to read at all" and "too many marks to draw legibly" are different problems with different remedies. The one non-negotiable rule: every trim must show a visible note (the kit writes one in a corner of the viz). Nothing should be dropped silently, because a chart that quietly shows half the data is worse than one that refuses and tells you why.

## FAQ

### Does a viz extension call getSummaryDataAsync?

A hosted viz component does not: it receives `data`, `schema`, and `fields` as props from the host. A hand-written viz extension calls `getVisualSpecificationAsync()` for the encoding-to-field mapping and then reads rows through the summary data reader.

### getSummaryDataAsync or getSummaryDataReaderAsync?

Use `getSummaryDataReaderAsync` for anything that might be large: it pages through the result instead of materialising it all at once, and you release it when done. `getSummaryDataAsync` returns everything in one call and is fine only for small, bounded data.

### Why are my totals wrong?

Almost always because you summed `formattedValue` (a display string) or indexed a column by position. Sum `nativeValue`, and find columns by field name or `dataType`, never by a hard-coded index that shifts when pills are reordered.

### How many rows should I read?

Cap it. A read cap around 100,000 rows before the first page protects Tableau, and a separate mark cap keeps the chart legible. Whatever caps you pick, surface a visible note whenever a cap trims the data so nothing is truncated silently.


---
Try it live: Build one free in Studio — https://ext.tableauops.com/studio
