Getting started
Install the SDK, load the Grist plugin API, and get a minimal widget rendering inside a real Grist document.
The fastest path to a running Grist widget is the Vite template. The rest of this page covers manual install and the smallest possible widget for readers who want to see the parts.
TL;DR
npm create grist-widget my-widget
cd my-widget
pnpm install
pnpm devOpen the printed URL — you see a placeholder telling you Grist is not available, because the dev server is running outside a Grist iframe.
To connect it to a real Grist document:
- Run
pnpm build && pnpm preview(or deploy thedist/folder to any static host — Cloudflare Pages, Netlify, GitHub Pages, etc.). - In a Grist document, add a Custom Widget section, paste the public URL into the URL field, and press Save.
- The widget renders inside Grist with full plugin-api access.
You now have a running widget. See Raw plugin API vs SDK
for why you use this package instead of calling grist directly. Skip ahead to the
Cookbook for ten end-to-end recipes, the
Cheat sheet for a one-page API reference, or
Troubleshooting if something is misbehaving.
What the template gives you
The template scaffolded above ships with:
- A Vite + React 19 + TypeScript app skeleton.
grist-widget-sdkand its peer dependencies pre-installed.- The host script tag for
grist-plugin-api.jsalready wired inindex.html. - A single-file
App.tsxthat demonstrates the recommended provider + boundary +useGrist()pattern. - Tailwind CSS preconfigured so the widget is theme-aware out of the box (light / dark / system).
- A test setup using vitest + the SDK emulator.
See the CLI reference for what the scaffold contains and how its bundled deploy pipeline works.
Manual install
Skip this section if you used the template.
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
in the playground is the canonical minimal widget. It is type-checked on
every pnpm --filter playground build:widgets 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>
}In your own Vite app, add GristWidgetProvider + GristBoundary around
WidgetApp (the template does this in main.tsx; the playground does it
in widget.html).
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.
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.
Where to go next
- Cookbook — ten end-to-end recipes for the most common widget shapes.
- Cheat sheet — one-page API reference for daily use.
- Troubleshooting — symptoms and fixes for the most common errors.
- CLI reference — what
npm create grist-widgetscaffolds. - Core concepts — mental model behind selection modes, mappings, and the ready handshake.
- Testing —
renderWithGristand the emulator.
What is `grist-widget-sdk`?
`grist-widget-sdk` is a React library that wraps the Grist plugin API — the JavaScript bridge between a custom widget iframe and a Grist document. Its goal is one thing:
Core concepts
A short mental model for the parts of Grist your widget interacts with, and how the SDK reflects them.