Skip to content

Column mapping

A widget should not depend on a user's exact column names. Instead, declare logical names your code uses; the user maps them to real columns through the section configuration UI.

Declare columns once

Pass columns to the provider:

tsx
<GristWidgetProvider
  options={{
    requiredAccess: "full",
    columns: [
      { name: "Title", type: "Text" },
      { name: "Done", type: "Bool" },
      {
        name: "Tags",
        type: "ChoiceList",
        allowMultiple: true,
        optional: true,
        description: "Optional tags to filter rows.",
      },
    ],
  }}
>

Field reference:

FieldNotes
nameRequired. Must be a valid JS identifier. This is the name your code uses.
titleShort label in the Grist mapping UI.
descriptionLong help text in the mapping UI.
typeComma-separated allowed Grist column types. Defaults to "Any".
optionalDefault false. Required columns gate mappedRecord.
allowMultipletrue lets the user map several real columns to one logical name. The corresponding mapping becomes string[].
strictTypetrue makes the type match exact (so "Any" only matches "Any").

You can also pass plain strings (columns: ["Title", "Done"]) when you don't need type hints.

Read mapped data

useGrist() exposes:

ts
const w = useGrist<MyMappedRow>()

w.mappedRecord      // logical-name view of the selected row (or null)
w.mappings          // { Title: "Name", Done: "isDone", Tags: ["tagA","tagB"] }
w.recordsMappings   // same for w.records
w.columnMappingStatus
//   { ok: boolean, missing: string[], emptyMultiples: string[] }

mappedRecord is null when any required column is unmapped. Never silently fall back to the raw record.

Typing

Type the SDK with two generics — your raw row shape (real column ids) and the mapped logical shape:

ts
type RawTask = {
  Name: string
  isDone: boolean
  tag_list: string[]
}

type MappedTask = {
  Title: string
  Done: boolean
  Tags?: string[]
}

const w = useGrist<RawTask, MappedTask>()
w.record?.Name          // typed
w.mappedRecord?.Title   // typed

See Typing your rows for more.

Gate UI on missing mappings

tsx
if (!w.columnMappingStatus.ok) {
  return (
    <p>
      Map these columns first: {w.columnMappingStatus.missing.join(", ")}
    </p>
  )
}

The SDK also exposes getGristSdkAlertDescriptors(w) which returns ready-to-render descriptors for the same condition, so you can wire your own Alert UI:

ts
const alerts = getGristSdkAlertDescriptors(w, {
  columnMappingHint: "Open the widget settings (gear icon) to map columns.",
})
// alerts: Array<{ id, kind, ariaRole, message }>

Write through the mapping

Writing back uses real column ids. Use w.mapBack(patch) to reverse the logical names:

ts
async function toggle() {
  const patch = w.mapBack({ Done: !w.mappedRecord!.Done })
  await w.table.update({
    id: w.record!.id,
    fields: patch,
  })
}

mapBack:

  • Uses grist.mapColumnNamesBack when available.
  • Falls back to the local mappings table for single-mapped columns.
  • Skips allowMultiple columns with a warning (you must choose which underlying column to write).

Resolve a single mapped column

For ad-hoc work, e.g. to set a Group by field:

ts
const col = w.resolveMappedColumnId("Tags")
// returns the first real id when allowMultiple, or the logical name as a last-resort fallback

Dynamic mappings

Mappings update as the user reconfigures the section. Subscribe via the same useGrist() — the slice that owns mappings re-renders by itself.

For one-off reads of the current mappings (e.g. as part of an action), use:

ts
const current = await w.refreshMappings()

To re-declare your widget's column requirements at runtime (rare, but supported for plugins like form builders):

ts
await w.configure({
  columns: [
    { name: "Title", type: "Text" },
    { name: "Body", type: "Text", optional: true },
  ],
})

Allow-multiple recipes

tsx
const tagCols = (w.mappings.Tags as string[] | undefined) ?? []
const tags = w.record
  ? tagCols.flatMap((col) => {
      const v = (w.record as Record<string, unknown>)[col]
      return typeof v === "string" ? [v] : []
    })
  : []

If you regularly read allowMultiple columns this way, consider modelling them with safeParseGristValues or per-column accessors in your widget code.

Released under the ISC License.