> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Column Types and EditConfig

> Guide for adopting the new type and editConfig properties in DataTable column definitions.

export const VersionStatus = ({version}) => {
  const isUnreleased = version === "unreleased";
  return <Badge color={isUnreleased ? "orange" : "green"}>
      {isUnreleased ? "Unreleased" : `v${version}`}
    </Badge>;
};

<VersionStatus version="2.0.3" />

<Note>
  This guide covers changes to the **beta** DataTable component. These APIs may continue to evolve before the stable release.
</Note>

## Overview

The DataTable column helper now supports two new properties that improve developer experience:

* **`type`** - Automatically configures display formatting and alignment based on data type
* **`editConfig`** - Consolidates cell editing configuration into a single, explicit opt-in property

These changes reflect our design philosophy of **providing sensible defaults while maintaining full customization**. Rather than requiring explicit configuration for common patterns, the new API infers reasonable defaults that you can override when needed.

## Design Rationale

### Why `type`?

Previously, configuring a currency column required setting multiple independent properties:

```tsx theme={null}
// Before: Manual configuration for each concern
createColumn("amount", {
  headerLabel: "Amount",
  align: "end",
  renderCell: (value) => currencyFormatter(value),
});
```

These properties are conceptually related - a currency column should right-align and use currency formatting. The new `type` property captures this relationship:

```tsx theme={null}
// After: Single property sets sensible defaults
createColumn("amount", {
  headerLabel: "Amount",
  type: "currency", // Sets align: "end" and currencyFormatter automatically
});
```

### Why `editConfig`?

The previous API scattered edit-related properties across the column definition:

```tsx theme={null}
// Before: Edit properties spread across the config
createColumn("status", {
  headerLabel: "Status",
  editMode: "select",
  options: [...],
  onChange: handleChange,
});
```

This made it unclear which properties were related and whether a column was editable. The new `editConfig` property makes the intent explicit:

```tsx theme={null}
// After: All edit concerns grouped together
createColumn("status", {
  headerLabel: "Status",
  editConfig: {
    mode: "select",
    options: [...],
    onChange: handleChange,
  },
});
```

Importantly, **editing is now opt-in**. Without `editConfig`, columns are read-only regardless of `type`.

### Extensibility

This structure supports new functionality without returning to scattered top-level edit props:

* **New column types** (e.g., `link`, `avatar`, `progress`) can be added with appropriate defaults
* **Shipped edit modes** already share one configuration surface: `text`, `number`, `boolean`, `select`, `multiselect`, and `custom`
* **Mode-specific options** (like `minValue`, `maxValue`, and `step` for number mode, or `renderEditor` for custom mode) live directly inside `editConfig`

## Column Types Reference

Each type sets default values for `align` and `renderCell`:

| Type       | Default Align | Default Formatter   |
| ---------- | ------------- | ------------------- |
| `text`     | `start`       | None (raw value)    |
| `number`   | `end`         | `numberFormatter`   |
| `currency` | `end`         | `currencyFormatter` |
| `percent`  | `end`         | `percentFormatter`  |
| `date`     | `end`         | `dateFormatter`     |
| `dateTime` | `end`         | `dateTimeFormatter` |
| `time`     | `end`         | `timeFormatter`     |
| `boolean`  | `start`       | `booleanFormatter`  |

### Using Type with Custom Options

For simple cases, pass the type as a string:

```tsx theme={null}
createColumn("amount", {
  headerLabel: "Amount",
  type: "currency",
});
```

To customize formatter options, pass an object:

```tsx theme={null}
createColumn("amount", {
  headerLabel: "Amount",
  type: { 
    type: "currency", 
    options: { currency: "EUR", locale: "de-DE" } 
  },
});
```

### Overriding Type Defaults

Explicit properties always override type defaults:

```tsx theme={null}
createColumn("amount", {
  headerLabel: "Amount",
  type: "currency",
  align: "start", // Overrides the default "end"
  renderCell: (value) => `$${value.toFixed(0)}`, // Overrides currencyFormatter
});
```

## EditConfig Reference

The `editConfig` property accepts an object with `mode`, `onChange`, and mode-specific options. Today that includes `text`, `number`, `boolean`, `select`, `multiselect`, and `custom`.

### Text Mode

```tsx theme={null}
createColumn("name", {
  headerLabel: "Name",
  editConfig: {
    mode: "text",
    onChange: (value, rowId) => updateName(value, rowId),
  },
});
```

### Select Mode

```tsx theme={null}
createColumn("status", {
  headerLabel: "Status",
  editConfig: {
    mode: "select",
    options: [
      { id: "pending", label: "Pending" },
      { id: "active", label: "Active" },
      { id: "completed", label: "Completed" },
    ],
    onChange: (option, rowId) => {
      if (option) updateStatus(String(option.id), rowId);
    },
  },
});
```

### Multiselect Mode

```tsx theme={null}
createColumn("tags", {
  headerLabel: "Tags",
  editConfig: {
    mode: "multiselect",
    options: [
      { id: "urgent", label: "Urgent" },
      { id: "reviewed", label: "Reviewed" },
    ],
    onChange: (options, rowId) => {
      updateTags(options.map((option) => String(option.id)), rowId);
    },
  },
});
```

### Custom Mode

Custom mode is for object-backed cells whose read view and edit view are intentionally different. It adds `renderEditor` for the custom editor surface, while `renderCell` and `getReadRenderResult` continue to define the read path. `renderEditor` now receives a typed `controller` for draft mutation, validation, focus management, and explicit close requests.

```tsx theme={null}
createColumn("address", {
  headerLabel: "Address",
  renderCell: (value) => formatAddress(value),
  getReadRenderResult: (value) => ({
    content: formatAddress(value),
    rawString: formatAddressForSort(value),
  }),
  editConfig: {
    mode: "custom",
    onChange: (value, rowId) => saveAddress(value, rowId),
    renderEditor: ({ controller }) => <AddressEditor controller={controller} />,
  },
});
```

See [Custom Edit Mode](/docs/web/components/data-table/beta-changes/custom-edit-mode) for the full custom-mode behavior, controller contract, close policy, and surface options.

## Migration Guide

### Display-Only Columns

No changes required. Optionally add `type` for automatic formatting:

```tsx theme={null}
// Before
createColumn("amount", {
  headerLabel: "Amount",
  renderCell: (value) => currencyFormatter(value),
});

// After (optional improvement)
createColumn("amount", {
  headerLabel: "Amount",
  type: "currency",
});
```

### Editable Columns

Move `editMode`, `onChange`, and `options` into `editConfig`. Custom editing is also configured only through `editConfig`; there is no legacy top-level custom API:

```tsx theme={null}
// Before
createColumn("name", {
  headerLabel: "Name",
  editMode: "text",
  onChange: handleChange,
});

// After
createColumn("name", {
  headerLabel: "Name",
  editConfig: {
    mode: "text",
    onChange: handleChange,
  },
});
```

For select/multiselect columns:

```tsx theme={null}
// Before
createColumn("status", {
  headerLabel: "Status",
  editMode: "select",
  options: statusOptions,
  onChange: handleChange,
});

// After
createColumn("status", {
  headerLabel: "Status",
  editConfig: {
    mode: "select",
    options: statusOptions,
    onChange: handleChange,
  },
});
```

## Deprecation Timeline

The following props show deprecation warnings in development:

* `editMode` - Use `editConfig.mode` instead
* `onChange` (top-level) - Use `editConfig.onChange` instead
* `options` (top-level) - Use `editConfig.options` instead

**Existing code continues to work** but will log console warnings to guide migration. TypeScript also marks these props with `@deprecated` JSDoc comments.

## New Exports

The following types are now exported from `@servicetitan/anvil2`:

* `ColumnType` - Union of column type literals
* `ColumnTypeConfig` - Type configuration (string or object with options)
* `EditConfig`, `TextEditConfig`, `SelectEditConfig`, `MultiselectEditConfig` - Edit configuration types
* `getColumnTypeDefaults()`, `resolveColumnTypeConfig()` - Utility functions for working with column types
