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 (Date → Date, 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.