Skip to content

useGrist

The primary hook. Returns a single object with every commonly-needed Grist capability.

ts
function useGrist<TRow = GristRecordFields, TMapped = TRow>(
  options?: UseGristOptions,
): UseGristResult<TRow, TMapped>

Behavior:

  • Inside <GristWidgetProvider>, useGrist() returns the shared context slices — no extra work, no duplicate subscriptions. options are ignored (the provider owns the configuration).
  • Outside <GristWidgetProvider>, useGrist() mounts its own private subscriptions (single-leaf widget mode) using the options argument. Prefer the provider in app code.
  • The slice hooks (useGristStatus, useGristSelection, useGristWrites, useGristTheme) are provider-only and throw when called outside <GristWidgetProvider>.

UseGristOptions

A superset of GristReadyOptions (see <GristWidgetProvider>):

ts
type UseGristOptions = GristReadyOptions & {
  // No additional fields today. Reserved for future hook-only knobs.
}

UseGristResult<TRow, TMapped>

Every field is annotated with its minimum access level. Set requiredAccess on your UseGristOptions / <GristWidgetProvider> accordingly — calling a method that needs "full" when you declared "read table" will fail at runtime.


Access: "none"

Available without any document access. Useful for connection status and theming.

FieldTypeNotes
status"booting" | "unavailable" | "ready" | "error"Connection state.
isAvailableboolean | nulltrue if the page is in a Grist iframe.
isReadybooleanConvenience for status === "ready".
errorstring | nullConnection-level error string.
reload() => Promise<void>Re-run the handshake.
capabilitiesGristCapabilitiesHandshake-derived gates — see below.
theme"light" | "dark" | nullResolved Grist theme.

Capabilities

capabilities is projected from the same snapshot as useGristHandshake(). Use it for UI gates without importing /advanced.

FieldMeaning
canReadOnline, link ok, read access granted.
canRendercanRead and mappings usable (complete or not_declared).
canWriteRecordscanRender + full document access.
canWriteSchemacanWriteRecords + current table id known.
canFetchTablecanRead + link connected.
hasFreshSelectionHot record stream + mappings ok.
hasUsableMappingsMapping state is complete or not declared.
missingMappingsLogical column names still unmapped.
emptyMultipleMappingsallowMultiple columns mapped to [].

Pair with <GristBoundary gate="canRender"> when the widget requires declared columns.


Access: "read table"

Requires at least Read selected table access.

Selection

FieldTypeNotes
recordGristRowRecord<TRow> | nullCurrently selected row (raw column ids).
recordsGristRowRecord<TRow>[] | nullAll visible rows in the section.
mappedRecordTMapped | nullSelected row under logical names. null if required mappings missing.
mappingsRecord<string, string | string[]>logical → real for the single-record stream.
recordsMappingsRecord<string, string | string[]>logical → real for the multi-record stream.
newRecordMappingsRecord<string, string | string[]>logical → real for the new-row placeholder.
mode"empty" | "row" | "new-row"Derived from selection state.
isNewRecordbooleanmode === "new-row".
columnMappingStatus{ ok: boolean; missing: string[]; emptyMultiples: string[] }Required-column gating.
widgetInteractionRecord<string, unknown> | nullSecond arg of onOptions, includes access_level.
mapBack(patch)(patch: Partial<TMapped>) => Record<string, unknown>Reverse-map a logical patch.
mapBackSkippedreadonly string[]Logical names that the last mapBack could not reverse-map (e.g. allowMultiple columns). Powers the map-back-skip alert descriptor.
resolveMappedColumnId(name)(name: string) => stringOne-shot column resolution (returns name as a fallback).

Current table

FieldTypeNotes
currentTableIdstring | nullCurrent section's table id.
currentTableLoadingbooleanTrue while the SDK re-reads selectedTable.getTableId().
refreshCurrentTable() => Promise<void>Force a re-read.

Reads (section-scoped)

FieldTypeNotes
fetchSelectedTable(opts?)row-shaped read of the current section
fetchSelectedRecord<T>(rowId, opts?)row of the current section
listTables(options?)(opts?: ListTablesOptions) => Promise<string[]>System/hidden tables filtered by default.
getDocName()() => Promise<string>
readErrorstring | nullLast access-insufficient read error. Drives the access-insufficient SDK alert.

Widget options (reads)

FieldType
widgetOptionsRecord<string, unknown> | null
getWidgetOptions()Promise<Record<string, unknown>>
getWidgetOption(key)Promise<unknown>

Linking

FieldType
setCursorPosition(pos)(pos: GristCursorPos) => Promise<void>
setLinkedRowSelection(ids | null)(rows: number[] | null) => Promise<void>

Section API

FieldType
configure(options)(GristReadyOptions) => Promise<void>
refreshMappings()Promise<Record<string, string | string[]>>

Access: "full"

Requires Full document access. Calling these methods with requiredAccess: "read table" will fail at runtime. The SDK guards them with a pre-flight access check — no RPC is made — and surfaces an access-insufficient alert with actionable instructions via readError / actionError.

Reads (full-document)

FieldTypeNotes
fetchTable<T>(tableId)(id) => Promise<Record<string, unknown[]>>Raw columnar payload for any table.
fetchTableRows<T>(id, opts?)row-shaped, decoded, optionally safe-parsedSee Reading data.
fetchRow<T>(id, rowId)row by id from any table
listColumns(tableId, options?)(id, opts?) => Promise<GristColumnInfo[]>Column metadata without fetching rows.
buildReplicaDocumentFromDocApi(opts?)(opts) => Promise<GristReplicaDocument>Bound version of the helper.

Writes (records)

FieldTypeNotes
tableGristTableOperationsCRUD on the current table (create, update, upsert, destroy).
getTable(id)(id: string) => GristTableOperationsMemoized CRUD for any table.
actionStatus"idle" | "running" | "error"Last write status.
actionErrorstring | nullLast write error message.

Writes (schema)

FieldTypeNotes
applyActions(actions: GristActionTuple[], options?: { desc?: string }) => Promise<unknown>Apply user-action tuples atomically.

Widget options (writes)

FieldType
setWidgetOption(key, value)Promise<void>
setWidgetOptions(next)Promise<void>
patchWidgetOptions(patch)Promise<void>
clearWidgetOptions()Promise<void>

Attachments / REST

FieldType
getAttachmentUrl(id, opts?)Promise<string>
fetchAttachmentBlob(id, opts?)Promise<{ blob: Blob; contentType: string }>
fetchAttachmentBase64(id, opts?)Promise<{ base64: string; contentType: string }>
getAccessToken(opts?)Promise<{ token: string; baseUrl: string }>
fetchWithAuth(path, init?)(path, init?) => Promise<Response>

opts.readOnly is supported on each of these. The path for fetchWithAuth is relative to the document API root (baseUrl from getAccessToken).

Examples

Selection + write

tsx
function Toggle() {
  const w = useGrist<{ Done: boolean }, { Done: boolean }>()

  if (w.mode !== "row") return null

  return (
    <button
      disabled={w.actionStatus === "running"}
      onClick={() =>
        w.table.update({
          id: w.record!.id,
          fields: w.mapBack({ Done: !w.mappedRecord!.Done }),
        })
      }
    >
      {w.mappedRecord!.Done ? "Mark not done" : "Mark done"}
    </button>
  )
}

Status + retry

tsx
function StatusLine() {
  const { status, error, reload } = useGrist()

  if (status === "ready") return null
  if (status === "error") return (
    <p>Error: {error} <button onClick={reload}>Retry</button></p>
  )
  if (status === "unavailable") return <p>Open this widget inside a Grist document.</p>
  return <p>Connecting…</p>
}

Schema mutation

tsx
async function migrate(w: ReturnType<typeof useGrist>) {
  await w.applyActions(
    [
      gristAddColumnAction("Tasks", "Priority", { type: "Choice" }),
      gristRenameColumnAction("Tasks", "Title", "Name"),
    ],
    { desc: "Add Priority column" },
  )
}

REST

ts
const r = await w.fetchWithAuth("/api/docs/.../sql", {
  method: "POST",
  body: JSON.stringify({ sql: "SELECT id, Name FROM Tasks LIMIT 5" }),
})
const data = await r.json()

Released under the ISC License.