useGrist
The primary hook. Returns a single object with every commonly-needed Grist capability.
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.optionsare ignored (the provider owns the configuration). - Outside
<GristWidgetProvider>,useGrist()mounts its own private subscriptions (single-leaf widget mode) using theoptionsargument. 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>):
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.
| Field | Type | Notes |
|---|---|---|
status | "booting" | "unavailable" | "ready" | "error" | Connection state. |
isAvailable | boolean | null | true if the page is in a Grist iframe. |
isReady | boolean | Convenience for status === "ready". |
error | string | null | Connection-level error string. |
reload | () => Promise<void> | Re-run the handshake. |
capabilities | GristCapabilities | Handshake-derived gates — see below. |
theme | "light" | "dark" | null | Resolved Grist theme. |
Capabilities
capabilities is projected from the same snapshot as useGristHandshake(). Use it for UI gates without importing /advanced.
| Field | Meaning |
|---|---|
canRead | Online, link ok, read access granted. |
canRender | canRead and mappings usable (complete or not_declared). |
canWriteRecords | canRender + full document access. |
canWriteSchema | canWriteRecords + current table id known. |
canFetchTable | canRead + link connected. |
hasFreshSelection | Hot record stream + mappings ok. |
hasUsableMappings | Mapping state is complete or not declared. |
missingMappings | Logical column names still unmapped. |
emptyMultipleMappings | allowMultiple 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
| Field | Type | Notes |
|---|---|---|
record | GristRowRecord<TRow> | null | Currently selected row (raw column ids). |
records | GristRowRecord<TRow>[] | null | All visible rows in the section. |
mappedRecord | TMapped | null | Selected row under logical names. null if required mappings missing. |
mappings | Record<string, string | string[]> | logical → real for the single-record stream. |
recordsMappings | Record<string, string | string[]> | logical → real for the multi-record stream. |
newRecordMappings | Record<string, string | string[]> | logical → real for the new-row placeholder. |
mode | "empty" | "row" | "new-row" | Derived from selection state. |
isNewRecord | boolean | mode === "new-row". |
columnMappingStatus | { ok: boolean; missing: string[]; emptyMultiples: string[] } | Required-column gating. |
widgetInteraction | Record<string, unknown> | null | Second arg of onOptions, includes access_level. |
mapBack(patch) | (patch: Partial<TMapped>) => Record<string, unknown> | Reverse-map a logical patch. |
mapBackSkipped | readonly 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) => string | One-shot column resolution (returns name as a fallback). |
Current table
| Field | Type | Notes |
|---|---|---|
currentTableId | string | null | Current section's table id. |
currentTableLoading | boolean | True while the SDK re-reads selectedTable.getTableId(). |
refreshCurrentTable | () => Promise<void> | Force a re-read. |
Reads (section-scoped)
| Field | Type | Notes |
|---|---|---|
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> | |
readError | string | null | Last access-insufficient read error. Drives the access-insufficient SDK alert. |
Widget options (reads)
| Field | Type |
|---|---|
widgetOptions | Record<string, unknown> | null |
getWidgetOptions() | Promise<Record<string, unknown>> |
getWidgetOption(key) | Promise<unknown> |
Linking
| Field | Type |
|---|---|
setCursorPosition(pos) | (pos: GristCursorPos) => Promise<void> |
setLinkedRowSelection(ids | null) | (rows: number[] | null) => Promise<void> |
Section API
| Field | Type |
|---|---|
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)
| Field | Type | Notes |
|---|---|---|
fetchTable<T>(tableId) | (id) => Promise<Record<string, unknown[]>> | Raw columnar payload for any table. |
fetchTableRows<T>(id, opts?) | row-shaped, decoded, optionally safe-parsed | See 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)
| Field | Type | Notes |
|---|---|---|
table | GristTableOperations | CRUD on the current table (create, update, upsert, destroy). |
getTable(id) | (id: string) => GristTableOperations | Memoized CRUD for any table. |
actionStatus | "idle" | "running" | "error" | Last write status. |
actionError | string | null | Last write error message. |
Writes (schema)
| Field | Type | Notes |
|---|---|---|
applyActions | (actions: GristActionTuple[], options?: { desc?: string }) => Promise<unknown> | Apply user-action tuples atomically. |
Widget options (writes)
| Field | Type |
|---|---|
setWidgetOption(key, value) | Promise<void> |
setWidgetOptions(next) | Promise<void> |
patchWidgetOptions(patch) | Promise<void> |
clearWidgetOptions() | Promise<void> |
Attachments / REST
| Field | Type |
|---|---|
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
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
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
async function migrate(w: ReturnType<typeof useGrist>) {
await w.applyActions(
[
gristAddColumnAction("Tasks", "Priority", { type: "Choice" }),
gristRenameColumnAction("Tasks", "Title", "Name"),
],
{ desc: "Add Priority column" },
)
}REST
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()