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

# Select Table

> Asks to pick rows of a table.

`io.select.table()` shows records as a table with a radio or a checkbox in front of each row and returns the rows that were picked. Provided by the `@awaitio/select-table` package.

<Frame>
  <img src="https://mintcdn.com/awaitio/MjbT5AW66vMXbVck/images/io-select-table.png?fit=max&auto=format&n=MjbT5AW66vMXbVck&q=85&s=cf0fc3015756a372fc3648d7a8a7c5b8" alt="A table with Name, Email and Role columns, a filter above it and a radio in front of every row, Anna picked" width="1404" height="560" data-path="images/io-select-table.png" />
</Frame>

The table has the same filter, pager and value formatting as [`io.display.table`](/io/display-table).

## Install

```bash theme={null}
npm install @awaitio/select-table
```

## Setup

Register it under the key you want in `io`, see [Input components](/core/components):

```ts app.ts {5} theme={null}
import { selectTable } from '@awaitio/select-table';

export const app = new AwaitIO({
  // name, actionsDirectory, theme, secret, users…
  components: { select: { table: selectTable } },
});
```

## Usage

```ts theme={null}
const [user] = await io.select.table('User', {
  rows: await prisma.user.findMany(),
  columns: [
    { key: 'name', label: 'Name' },
    { key: 'email', label: 'Email' },
  ],
});
```

## Props

| Prop | Type | Required | Description |
| - | - | - | - |
| `rows` | `Record<string, unknown>[]` | Required | The records to pick from, at least one. |
| `columns` | `(string \| { key, label })[]` | Optional | Which keys to show and their headings. Defaults to every key. |
| `defaultValue` | `Record<string, unknown>[]` | Optional | Rows picked at the start, taken from `rows`. |
| `minSelections` | `number` | Optional | At least this many rows. Defaults to 1. |
| `maxSelections` | `number` | Optional | At most this many rows. Defaults to 1, which shows radios instead of checkboxes; with more, a pick past the limit is ignored. |
| `pageSize` | `number` | Optional | Rows per page. Defaults to 10. |
| `helpText` | `string` | Optional | Secondary text under the table. |
| `disabled` | `boolean` | Optional | Shows the starting value without letting it change; the handler gets that value whatever the browser sends. |

## Optional

The field is required. Chain `.optional()` to let it stay empty; the handler then gets `Record<string, unknown>[] | undefined`, see [Optional fields](/core/components#optional-fields).

```ts theme={null}
const notified = await io.select.table('Users to notify', { rows: users, maxSelections: 5 }).optional();
```

## Returns

`Record<string, unknown>[]`: the picked rows as they were sent to the browser. They travel as JSON, so a `Date` comes back as its ISO string; read the record again by its id when you need all of it.

## Built-in validation

The server checks every answer, so a crafted request cannot slip in another row:

| When | Message |
| - | - |
| No row picked, by default | `<label>: pick a row.` |
| Fewer than `minSelections` | `<label>: pick at least <minSelections> rows.` |
| More than `maxSelections` | `<label>: pick at most <maxSelections> rows.` |
| A row that was not in the table | `<label>: pick rows from the table only.` |

## Custom validation

Chain `.validate()` for a check of your own. It runs on the server after the checks above, gets the value the handler would get and returns a message to show under the field, or nothing when the value is fine; see [Custom validation](/core/components#custom-validation).

```ts theme={null}
const [user] = await io.select.table('User to remove', { rows: users }).validate((rows) => {
  return rows.some((row) => row.role === 'admin') ? 'Admins cannot be removed here.' : undefined;
});
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.