Columns
The columns prop defines every column in the grid. Each entry is a DataEditorColumn object.
<DataEditor columns={columns} ... />Column Shape
Section titled “Column Shape”type DataEditorColumn = { id: string; title: string; editor?: CellEditor; validators?: ValidatorRule[]; unique?: boolean; dependentFields?: string[]; formatter?: (value: string) => string; transformer?: (value: unknown) => unknown; filter?: ColumnFilter; pinnable?: boolean; size?: number; locked?: boolean | "all" | "default";};| Type | string |
| Required | Yes |
Unique column identifier. Must match the keys in your row data.
| Type | string |
| Required | Yes |
Column header text shown to the user.
editor
Section titled “editor”| Type | CellEditor |
| Default | { type: "text" } |
Controls how the cell is edited. Four types available:
CellEditor
Section titled “CellEditor”type CellEditor = | { type: "text" } | { type: "date"; minDate?: Date; maxDate?: Date } | { type: "select"; options: string[] } | { type: "number"; decimalPlaces?: number; decimalSeparator?: string; thousandsSeparator?: string; allowChars?: string };Text (default)
Section titled “Text (default)”Plain text input.
{ type: "text" }Date picker with optional min/max bounds.
{ type: "date", minDate: new Date("2020-01-01"), maxDate: new Date("2030-12-31") }Select
Section titled “Select”Dropdown with a fixed list of options. Each string is both the stored value and the display label.
{ type: "select", options: ["Admin", "Editor", "Viewer"] }Number
Section titled “Number”Number input with locale-aware formatting.
{ type: "number", decimalPlaces: 2, decimalSeparator: ".", thousandsSeparator: ",", allowChars: "%-",}| Field | Type | Description |
|---|---|---|
decimalPlaces | number | Maximum decimal digits. Unrestricted when omitted. |
decimalSeparator | string | Decimal point character. Defaults to browser locale. |
thousandsSeparator | string | Thousands grouping character. Defaults to browser locale. |
allowChars | string | Extra characters to allow beyond digits, decimal separator, and minus sign. |
validators
Section titled “validators”| Type | ValidatorRule[] |
One or more validators run on every edit. Each entry is a tagged object describing a rule. Built-in rules cover the common cases; the function rule is the escape hatch for anything custom.
ValidatorRule
Section titled “ValidatorRule”type ValidatorRule = | BuiltInValidator | { type: "function"; fn: CellValidator };ValidationError
Section titled “ValidationError”type ValidationError = { level: "error"; message: string;};A ValidationError with level: "error" flags the cell in the grid but does not block submission — invalid rows are delivered to onComplete alongside valid ones, tagged via the isValid flag.
Built-in validators
Section titled “Built-in validators”Each rule is an object literal. The optional message overrides the default localized error text.
{ type: "required" }
Section titled “{ type: "required" }”Rejects empty, null, or undefined values.
{ type: "required", message: "Name is required" }{ type: "numeric" }
Section titled “{ type: "numeric" }”Rejects non-numeric values. Handles comma and dot separators.
{ type: "numeric", message: "Must be a number" }{ type: "email" }
Section titled “{ type: "email" }”Validates email format.
{ type: "email", message: "Invalid email address" }{ type: "date" }
Section titled “{ type: "date" }”Validates YYYY-MM-DD or DD/MM/YYYY formats. Pass format to require a specific one.
{ type: "date", format: "YYYY-MM-DD", message: "Invalid date" }{ type: "oneOf" }
Section titled “{ type: "oneOf" }”Restricts to a set of allowed values.
{ type: "oneOf", values: ["Active", "Inactive"], message: "Must be Active or Inactive" }{ type: "regex" }
Section titled “{ type: "regex" }”Validates against a regular expression. pattern is a string compiled at runtime; flags is optional.
{ type: "regex", pattern: "^\\+[\\d\\s]+$", message: "Invalid phone number" }{ type: "range" }
Section titled “{ type: "range" }”Restricts a numeric value to a min/max range. Both bounds are optional.
{ type: "range", min: 0, max: 1_000_000, message: "Must be between 0 and 1,000,000" }Custom validation
Section titled “Custom validation”When a built-in doesn’t fit, drop down to { type: "function" }. The fn receives the cell value and the full row; return a ValidationError to flag a problem, or null if valid.
type CellValidator = (value: unknown, row: DataEditorRow) => ValidationError | null;
{ type: "function", fn: (value, row) => row.country === "US" && !/^\d{5}$/.test(String(value)) ? { level: "error", message: "US ZIP must be 5 digits" } : null,}Cross-field validation
Section titled “Cross-field validation”Use dependentFields together with a function rule to revalidate this column when another column changes:
{ id: "endDate", title: "End Date", editor: { type: "date" }, validators: [{ type: "function", fn: (value, row) => new Date(String(value)) <= new Date(String(row.startDate)) ? { level: "error", message: "End date must be after start date" } : null, }], dependentFields: ["startDate"],}unique
Section titled “unique”| Type | boolean |
When true, the editor flags duplicate values in this column as errors. The error message is localized via the translations prop (dataEditor.validation.valueMustBeUnique).
dependentFields
Section titled “dependentFields”| Type | string[] |
Column IDs to revalidate when this column changes.
formatter
Section titled “formatter”| Type | (value: string) => string |
Format the display value without changing stored data.
{ formatter: (v) => v ? `$${v}` : "" }transformer
Section titled “transformer”| Type | (value: unknown) => unknown |
Transform a value before it enters the store. Runs when rows are uploaded to the data editor.
{ transformer: (v) => typeof v === "string" ? v.trim() : v }filter
Section titled “filter”| Type | ColumnFilter |
Adds a filter control for this column in the sidebar Filters panel.
ColumnFilter
Section titled “ColumnFilter”type ColumnFilter = | { type: "select"; label?: string; placeholder?: string; options?: string[]; multiple?: boolean } | { type: "number-range"; label?: string } | { type: "date-range"; label?: string };Select Filter
Section titled “Select Filter”Dropdown to pick one or more values.
{ type: "select", label: "Status", placeholder: "All statuses", options: ["Active", "Inactive"], multiple: true }| Field | Type | Description |
|---|---|---|
label | string | Label above the filter. |
placeholder | string | Placeholder when nothing selected. |
options | string[] | Fixed options. When omitted, derived from column values. |
multiple | boolean | Allow multiple selections. |
Number Range Filter
Section titled “Number Range Filter”Two inputs for min and max.
{ type: "number-range", label: "Salary Range" }Date Range Filter
Section titled “Date Range Filter”Two date pickers for start and end date.
{ type: "date-range", label: "Date Range" }pinnable
Section titled “pinnable”| Type | boolean |
| Default | true |
Whether this column can be pinned to the left (right in RTL) via the header context menu.
| Type | number |
| Default | 150 |
Column width in pixels.
locked
Section titled “locked”| Type | boolean | "all" | "default" |
Controls whether cells in this column are locked.
trueor"all"— locked for every row."default"— locked only for default-source rows. Rows added manually, duplicated, or imported remain editable.falseorundefined— not locked.
A column locked via configuration cannot be unlocked from the UI.
Complete Example
Section titled “Complete Example”import type { DataEditorColumn } from "@updog/data-editor";
const columns: DataEditorColumn[] = [ { id: "name", title: "Full Name", size: 200, validators: [{ type: "required", message: "Name is required" }], transformer: (v) => typeof v === "string" ? v.trim() : v, }, { id: "email", title: "Email", size: 250, validators: [ { type: "required", message: "Email is required" }, { type: "email", message: "Invalid email" }, ], unique: true, }, { id: "salary", title: "Salary", editor: { type: "number", decimalPlaces: 2 }, validators: [{ type: "numeric", message: "Must be a number" }], formatter: (v) => v ? `$${v}` : "", filter: { type: "number-range", label: "Salary" }, }, { id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] }, validators: [{ type: "oneOf", values: ["Admin", "Editor", "Viewer"], message: "Invalid role" }], filter: { type: "select", label: "Role", multiple: true }, }, { id: "startDate", title: "Start Date", editor: { type: "date" }, filter: { type: "date-range", label: "Start Date" }, }, { id: "endDate", title: "End Date", editor: { type: "date" }, validators: [{ type: "function", fn: (value, row) => new Date(String(value)) <= new Date(String(row.startDate)) ? { level: "error", message: "Must be after start date" } : null, }], dependentFields: ["startDate"], }, { id: "notes", title: "Notes", size: 300, locked: false, pinnable: false, },];