Skip to content

Reading data

There are two read paths in the SDK:

  1. Subscriptions — reactive, re-render on Grist updates.
  2. One-shot fetches — explicit calls you trigger from event handlers or effects.

Use subscriptions for the widget's primary view; use fetches for everything else.

Subscriptions

The provider subscribes to four Grist event streams under the hood:

StreamExposed asNotes
grist.onRecordw.record, w.mappingsSingle-row stream, decoded.
grist.onRecordsw.records, w.recordsMappingsAll visible rows in the section, decoded, row-shaped.
grist.onOptionsw.widgetOptions, w.widgetInteractionPersistent widget options.
grist.onNewRecordw.isNewRecord, w.newRecordMappingsThe blank "new row" placeholder.

Decoded means keepEncoded: false and format: "rows" — your code sees JS-native values, not ["D", 1700000000, "UTC"] tuples.

Single record

tsx
function RowPanel() {
  const w = useGrist<MyRow>()
  if (w.mode === "empty") return <p>Select a row.</p>
  return <pre>{JSON.stringify(w.record, null, 2)}</pre>
}

All records visible in the section

tsx
function Counts() {
  const w = useGrist()
  return <p>Visible rows: {w.records?.length ?? 0}</p>
}

Mapped record

w.mappedRecord is w.record rewritten under your logical names. See Column mapping.

New-row placeholder

tsx
if (w.mode === "new-row") return <CreateRowForm mappings={w.newRecordMappings} />

One-shot fetches

The hook returns several read methods. All return promises and throw if the SDK isn't ready (status !== "ready" — guard with <GristBoundary> to avoid).

MethodReturnsNotes
w.fetchTable(tableId)columnar { [col]: unknown[] }Raw docApi.fetchTable.
w.fetchTableRows(tableId, options?)GristRowRecord<T>[]Decoded rows, optional safe-parse.
w.fetchRow(tableId, rowId)GristRowRecord<T> | nullSingle row.
w.fetchSelectedTable(options?)columnarCurrent section's table.
w.fetchSelectedRecord(rowId, options?)rowCurrent section row by id.
w.listTables(options?)string[]User table ids (system/hidden tables filtered by default). Pass { includeSystem: true } for all.
w.listColumns(tableId, options?)GristColumnInfo[]Column metadata (colId, type, label, …) without fetching row data. Noise columns filtered by default.
w.getDocName()stringDocument identifier.

fetchTableRows with decode

ts
const rows = await w.fetchTableRows<MyTask>("Tasks", {
  // Provide schema for type-aware decoding (Dates → Date, Refs → { __ref, rowId, … }).
  columns: schema.columns,
  order: "grist", // "grist" | "id" | "none"; default "grist" (manualSort, fallback id)
})

Columns vs safeParse

These options are independent; do not pass safeParse unless you need per-cell issue tracking.

OptionWhat you get
columns onlyType-aware decode into plain row values (DateDate, Ref:* → ref objects, etc.). No ok / issues wrapper on cells.
safeParse: true (or options object)Decode + issue tracking — each cell is a GristSafeParseCellResult (ok, typedValue, issues, …).
BothValid — the safeParse materialization path runs (same as the safe-parse example below).

Use columns for normal reads. Add safeParse when you need to surface validation problems per cell (and optionally onSafeParseIssues).

fetchTableRows with safe parsing

When you want typed cells with per-cell issue tracking (e.g. choice/ref validation):

ts
const parsed = await w.fetchTableRows<MyTask>("Tasks", {
  columns: schema.columns,
  safeParse: true, // or pass options: { refMode: "detailed", fallbackUntypedToString: true, ... }
  onSafeParseIssues: (issues) => console.warn(issues),
})
// parsed: GristRowRecord<{ [K in keyof T]: GristSafeParseCellResult<T[K]> | null }>[]

Each cell becomes:

ts
type GristSafeParseCellResult<T> = {
  ok: boolean
  typedValue: T | null
  displayValue: unknown
  raw: unknown
  issues: Array<{ code: string; message: string }>
}

Project these back to plain rows with safeParseRowsToDisplayRows(parsed) when you only need displayValue.

Polling

The reactive w.records stream covers the current section. To watch a different table for changes, you have to poll — there is no system-wide row-change event.

The advanced hook useGristRowsFromTable does this for you:

ts
import { useGristRowsFromTable } from "grist-widget-sdk/advanced"

const { rows, refresh, loading, error } = useGristRowsFromTable<MyRow>({
  tableId: "Reports",
  pollIntervalMs: 10_000,
})

Schema reads for LLMs

When you need a structured snapshot of the whole document (e.g. as system context for a model), use useGristSchema() or w.buildReplicaDocumentFromDocApi(...):

ts
const schema = useGristSchema({
  excludeSystemTableData: true,
  replicaRowMode: "schema+samples",
  sampleRowLimit: 5,
})

console.log(schema.documentJson) // JSON string ready to feed an LLM

See useGristSchema and Replica document for the full data model.

Cell decoding helpers

For ad-hoc access to raw cells, two functions cover most cases:

ts
import { decodeGristValue, encodeGristValue } from "grist-widget-sdk"

const cell = decodeGristValue(rawValue, columnMeta)
// Date columns → Date, Ref columns → { __ref, rowId, tableId? }, etc.

const wireValue = encodeGristValue(date, columnMeta)
// Date → epoch seconds, RefList → ["L", id1, id2, ...]

For a typed parsing pipeline with issue reporting, prefer safeParseGristValues({ values, column, helperValues }).

Released under the ISC License.