> For the complete documentation index, see [llms.txt](https://docs.rowsncolumns.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rowsncolumns.app/configuration/features/linked-data-types.md).

# Linked data types

Linked data types are Excel's "Stocks" and "Geography" cells generalized into a framework. A cell holds an **entity** with named fields, and other cells reference those fields via dot notation (`=A1.price`). The library ships the entity model + a provider plug-in API; you wire your own data source.

This is how the storybook's "Insert stock at A1" button works — it writes an `entity` structured value into a cell, then `refreshDataTypes` walks every entity cell and calls the matching provider to refresh fields.

## The entity shape

A linked-data-type cell's `ev.structuredValue` is an entity:

```ts
type Entity = {
  kind: "entity";
  dataType: string;          // matches a DataTypeProvider.id
  externalId: string;        // the ticker / location / etc.
  status: "loading" | "loaded" | "stale" | "error";
  fields?: Record<string, string | number | boolean>;
  formattedValue?: string;
};
```

The cell renderer paints `formattedValue` (or `fv`) as the display text. Field-access formulas (`=A1.price`) resolve through `getEffectiveStructuredValue`.

## Wiring a provider

```ts
import type { DataTypeProvider } from "@rowsncolumns/spreadsheet-state";

const stockProvider: DataTypeProvider = {
  id: "stock",
  schema: () => [
    { field: "name", type: "string" },
    { field: "price", type: "number", numberFormat: '"$"#,##0.00' },
    { field: "currency", type: "string" },
    { field: "changePercent", type: "number", numberFormat: "0.00%" },
  ],
  async refresh(entity) {
    const ticker = (entity.externalId ?? "").toUpperCase();
    const row = await fetchStockQuote(ticker);
    return {
      fields: {
        name: row.name,
        price: row.price,
        currency: row.currency,
        changePercent: row.changePercent,
      },
      formattedValue: `${ticker}  $${row.price.toFixed(2)}`,
    };
  },
};
```

Pass providers to `useSpreadsheetState`:

```ts
const { refreshDataTypes } = useSpreadsheetState({
  dataTypeProviders: [stockProvider],
  getEffectiveStructuredValue: (structuredValue, property) => {
    if (property && structuredValue?.kind === "entity") {
      return resolveEntityField(structuredValue, property);
    }
    if (property) {
      const value = get(structuredValue, property);
      return value ?? "";
    }
    return structuredValue;
  },
});
```

`resolveEntityField` is exported from `@rowsncolumns/spreadsheet-state`; it reads `entity.fields[property]`.

## Inserting a linked data type

Write the entity shape into the cell's `ev.structuredValue`:

```ts
const tickers = ["AAPL", "MSFT", "GOOG"];
onChangeSheetData?.((prev) => {
  const next = { ...prev };
  const sheet = (next[activeSheetId] ?? []).slice();
  next[activeSheetId] = sheet;
  tickers.forEach((ticker, idx) => {
    const rowIndex = 1 + idx;
    const row = sheet[rowIndex] ?? {};
    const values = (row.values ?? []).slice();
    values[1] = {
      ev: {
        structuredValue: {
          kind: "entity",
          dataType: "stock",
          externalId: ticker,
          status: "stale",
          formattedValue: ticker,
        },
      },
      fv: ticker,
    };
    sheet[rowIndex] = { ...row, values };
  });
  return next;
});

// Now refresh so fields hydrate
await refreshDataTypes?.({ sheetId: activeSheetId });
```

After the refresh:

* `=A2.price` returns the numeric price.
* `=A2.changePercent` returns the percent (with format applied).
* `=SUM(A2:A4.price)` aggregates across multiple linked-data-type cells.

## Dot-notation field access

`=cellRef.field` resolves through the formula engine's structured-value branch. The field name matches a key in `entity.fields`. The library auto-applies the `numberFormat` declared in the provider's `schema()` to the result.

```
A2 entity {ticker: "AAPL", price: 182.50, currency: "USD"}

=A2                  → "AAPL  $182.50"   (cell's formattedValue)
=A2.price            → 182.50            (numeric)
=A2.currency         → "USD"             (string)
=A2.price * 100      → 18250
```

## Refresh model

`refreshDataTypes({sheetId})` walks every cell whose `structuredValue.kind === "entity"` and calls the matching provider's `refresh()`. Each refresh sets the cell's `status` to `"loading"` while in flight, then `"loaded"` (or `"error"`) when complete. Cells with downstream `=A1.field` formulas recompute automatically once the entity lands.

`status: "stale"` is the initial state for a freshly-inserted entity and triggers a refresh on the next `refreshDataTypes` call.

## What's not yet supported

* **OOXML `linkedDataType` round-trip** — Excel's native Stocks / Geography panels emit a specific element that's not yet parsed or emitted. Custom provider entities survive the export as plain JSON in `structuredValue` if the host writes them as such; Excel's native Stocks won't be recognized.

See [Excel compatibility](/getting-started/excel-google-sheet-compatibility.md) → "Phase 12 — linked data types" for the deferral notes.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rowsncolumns.app/configuration/features/linked-data-types.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
