SDK
Add grist-widget-sdk to an existing Vite or Next.js project, then the core hooks every widget uses.
grist-widget-sdk wraps the Grist plugin API — the JavaScript bridge
between a custom widget iframe and a Grist document — into one hook, a
provider, and a boundary. New here? What is grist-widget-sdk?
covers why it exists and when to reach for the raw plugin API instead.
Already have a scaffolded project from the template or the CLI? Its dependencies and plugin-api script tag are pre-wired — skip ahead to Hello world. This page is for adding the SDK to a project you already have.
Install
pnpm add grist-widget-sdk
# or
npm install grist-widget-sdk
# or
yarn add grist-widget-sdkPeer dependencies:
react >=18
react-dom >=18Load the Grist plugin API
The SDK expects the global grist object at runtime. Add the script in
your app shell:
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>Hello world
hello-world
is the canonical minimal widget. It is type-checked on every
pnpm typecheck:examples run (root pnpm test includes that step).
import { useGrist, type UseGristOptions } from "grist-widget-sdk"
export const GRIST_OPTIONS: UseGristOptions = {
requiredAccess: "read table",
}
export function WidgetApp() {
const w = useGrist()
const rowKey =
w.record && typeof w.record.id === "number" ? String(w.record.id) : w.mode
if (w.mode === "empty") return <p>Select a row.</p>
if (w.mode === "new-row") return <p>New row flow</p>
return <p key={rowKey}>Selected row #{String(w.record!.id)}</p>
}Wrap it with GristWidgetProvider + GristBoundary — the
template does exactly this in main.tsx. That's
everything you need to:
- Wait for Grist to finish its handshake.
- Render a friendly fallback when the page is opened outside Grist.
- Render an error UI if anything goes wrong, with a retry button.
- Subscribe to the currently selected row.
- Switch between
empty/row/new-rowmodes.
See Provider & boundary for what each
piece does, and Raw plugin API vs SDK
for why you use this package instead of calling grist directly.
What useGrist() returns
| Group | Properties |
|---|---|
| Status | status, isAvailable, isReady, error, reload() |
| Selection | record, records, mappedRecord, mode, mappings, columnMappingStatus, isNewRecord |
| Writes (records) | table, getTable(id), actionStatus, actionError |
| Writes (schema) | applyActions(actions) |
| Reads | fetchTable, fetchTableRows, fetchRow, fetchSelectedTable, fetchSelectedRecord, listTables, getDocName |
| Widget options | widgetOptions, getWidgetOptions, setWidgetOption, patchWidgetOptions, clearWidgetOptions |
| Linking | setCursorPosition, setLinkedRowSelection |
| Attachments / REST | getAttachmentUrl, fetchAttachmentBlob, uploadAttachment, getAccessToken, fetchWithAuth |
| Section API | configure, refreshMappings, currentTableId |
| Theme | theme ("light" | "dark" | null) |
See the API reference for every field.
Asking for write access and declared columns
declared-columns
shows GRIST_OPTIONS.columns and columnMappingStatus — full source in the
Cheat sheet under Mappings + column gate.
In the widget configuration panel, the user maps these logical names
to real columns. You consume them via w.mappedRecord and
w.mapBack(...) on writes — see Column mapping.
Run a write
mark-done is a single-row
table.update; its source is in the Cheat sheet.
For bulk writes, pass an array and the SDK forwards it to Grist as one
BulkUpdateRecord action — see
bulk-mark-done in the
Cookbook.
For schema changes, see schema-migration, also in the Cookbook.
Explore the rest of the SDK
Singleton hook
A single useGrist() handles all Grist states to avoid race conditions and code drift.
Robust handshake
Provider, boundary, and modes for clean errors and status management within your app.
Decoded values
JS-native cell shapes with explicit types, errors and safe parsing.
Mapped columns
mappedRecord and mapBack for simple read/write on Grist tables.
Cookbook
Ten end-to-end recipes — and the working demo widgets behind each one.
Design rationale
Why the API looks the way it does — for teams evaluating the trade-offs.
Guide contents
| Topic | What it covers |
|---|---|
| Raw plugin API vs SDK | Why use this package instead of calling grist directly |
| Column mapping | Declaring logical column names, letting users map them |
| Reading data | The two paths for reading data out of a document |
| Writing data | The two write paths, and when to use each |
| Widget options | Persisting per-section settings in Grist's own store |
| Attachments & REST | Attachment bytes, SQL, and the REST API via pre-authenticated fetch |
| Widget linking & theme | Driving another widget's cursor; adopting the document theme |
| Typing your rows | The two generics on useGrist() |
| Handshake state machine | How a widget and Grist stay in sync |
| Error handling | Where errors surface, and which layer to read |
| Performance | Keeping large widgets fast with slice hooks |
| Cheat sheet | Copy-pasteable shapes for daily reference |
| Troubleshooting | Symptom-first fixes |
| Cookbook | Ten ready-to-paste recipes |
See the API reference for every exported symbol, or Design for the rationale behind the API's shape.
Where to go next
- What is grist-widget-sdk? — why it exists, and when to reach for the raw plugin API instead.
- Core concepts — the mental model behind selection modes, mappings, and the ready handshake.
- CLI reference — scaffold a new project instead of adding the SDK to an existing one.
- Emulator — test widgets without a live Grist document.