Register one table or model as an admin resource.

```ts
export function resource<Ref, const Name extends string>(ref: Ref, options: ResourceOptions<InferRow<Ref>> & { name: Name; }): ResourceConfig<Ref, InferRow<Ref>, ResourceOptions<InferRow<Ref>> & { name: Name; }>;
```

```ts
export function resource<Ref>(ref: Ref, options: ResourceOptions<InferRow<Ref>>): ResourceConfig<Ref, InferRow<Ref>, ResourceOptions<InferRow<Ref>>>;
```

`ref` is whatever your adapter understands — a Drizzle table object, or a
Prisma model name. The row type comes from
[`InferRow<Ref>`](/docs/reference/registry#inferrow): a Drizzle table resolves
through `$inferSelect`, a string ref through your `FlowpanelResources`
augmentation, and anything else falls back to `Record<string, unknown>`. When
it resolves to a real row type, `columns`, `search`, `filters` and
`defaultSort` only accept real column names; when it falls back, they are
plain `string` and the check moves to
[`defineAdmin`'s introspect-time validation](/docs/build/resources#typos-and-column-validation).

```ts excerpt
import { resource } from "@flowpanel/kit";
import { orders } from "@/db/schema";

export const ordersResource = resource(orders, {
  label: "Order",
  plural: "Orders",
  icon: "shopping-cart",
  columns: ["id", "customerEmail", { field: "total", format: "money" }, "status"],
  search: ["customerEmail"],
  filters: ["status", { field: "createdAt", type: "daterange" }],
  defaultSort: { field: "createdAt", dir: "desc" },
});
```

## ResourceOptions

**ResourceOptions**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | no | URL segment and registry key. Defaults to the adapter's table name. |
| `label` | `string` | no | Singular label shown in headings and buttons. |
| `labelOne` | `string` | no | Label for exactly one row, e.g. `"Customer"`. Used where a heading talks
about a single record — the create page's title. Falls back to `label`,
then to the resource name. |
| `plural` | `string` | no | Plural label shown in the nav and list title. |
| `icon` | `IconName` | no | Serializable Lucide icon rendered in navigation and the command palette. |
| `hidden` | `boolean` | no | Keep the resource out of the navigation — its routes still work. |
| `expose` | `array` | no | Additional fields that may cross the generated server/client boundary. |
| `columns` | `array` | no | Columns of the list table, in order. A bare string is a field name.
Omitted, `defineAdmin` fills it with every column the adapter introspects,
in introspection order. |
| `search` | `array` | no | Fields the search box queries. Without this, search is disabled. |
| `filters` | `array` | no | Filter controls above the list. A bare string always renders a text filter — declare a `FilterDef` for any other control. |
| `defaultSort` | `object` | no | Sort applied when the URL carries none. |
| `pageSize` | `number` | no | Rows per page. |
| `density` | `"compact" \| "comfortable"` | no | Row height preset for the list table. |
| `rowClick` | `"drawer" \| false` | no | Open the row's drawer when its row is clicked. Requires `drawer`. |
| `rowKey` | `string & keyof Row` | no | Property holding each row's unique id. |
| `drawer` | `object` | no | Side panel opened for a single row. |
| `detail` | `object` | no | Full-page view at `/<basePath>/<resource>/<id>`. |
| `schema` | `object` | no | Validation for writes. Overrides the schema inferred from the adapter. |
| `create` | `object` | no | Create form. `disabled` removes the route, not just the button. |
| `update` | `object` | no | Edit form. `disabled` removes the route, not just the button. |
| `delete` | `object` | no | Delete behaviour. Set `softDelete` to a timestamp column to keep rows recoverable. |
| `actions` | `array` | no | Per-row actions, rendered inline or in the row's menu. |
| `bulkActions` | `array` | no | Actions applied to a selection of rows. |
| `scope` | `function \| "bypass"` | no | How the admin-wide `scope` narrows this resource. `"bypass"` opts out explicitly. |
| `access` | `object` | no | Canonical operation-level authorization. |
| `fieldAccess` | `Partial<object>` | no | Canonical read/write policy for declared fields. |
| `requireRole` | `function \| array \| string` | no |  |
| `export` | `object \| false` | no | Export button. `false` removes it. Defaults to CSV and JSON of the visible columns. |
| `import` | `object \| false` | no | Import button. Off unless set; rows are created through the normal write path. |
| `audit` | `boolean` | no | Opt this resource out of the admin-wide audit sink. |
| `realtime` | `boolean \| string` | no | Refresh the list when the channel fires. `true` uses `resource.<name>`. |
| `views` | `array` | no | Filter / sort presets offered as a dropdown above the list. |
| `empty` | `object` | no | What the list shows when there are no rows at all. |

### Icons

`ResourceOptions.icon`, dashboard/page/queue icons, action icons and
`CommandItem.icon` all use the same serializable `IconName` union. FlowPanel
turns the name into a decorative Lucide SVG in the sidebar, tab strip and ⌘K
palette, so the config remains safe to pass through a React Server Component
boundary. Unknown names are rejected by TypeScript instead of disappearing at
runtime. Built-in names include `users`, `settings`, `layout-dashboard`,
`database`, `shopping-cart`, `archive`, `refresh`, `play`, `ban` and
`trash-2`; editor autocomplete shows the complete `IconName` set.

For custom pages and slots, render the same registry directly:

```tsx excerpt
import { FlowpanelIcon } from "@flowpanel/kit/react";

<FlowpanelIcon name="workflow" className="size-4" />;
```

## ColumnDef

An entry in `columns`. A bare string is shorthand for `{ field: "name" }`.

**ColumnDef**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `field` | `string & keyof Row` | no | Row property this column reads. Omit only when `render` supplies the cell. |
| `label` | `string` | no | Header text. Defaults to a humanized `field`. |
| `render` | `function` | no | Custom cell renderer. Runs on the server; receives the request context. |
| `sortable` | `boolean` | no | Allow clicking the header to sort by this column. |
| `width` | `number \| string` | no | Fixed column width, in px when a number. |
| `align` | `"right" \| "center" \| "left"` | no | Horizontal cell alignment. Defaults to `"left"`. |
| `className` | `string` | no | Extra classes on every cell in this column. |
| `hidden` | `boolean` | no | Keep the column out of the table (still available to export and drawer). |
| `format` | `ColumnFormat` | no | Declarative cell formatting — use instead of `render` where it fits. |
| `reference` | `object` | no | Resolve a foreign key to a label from another resource. |
| `editable` | `boolean` | no | Allow editing this cell inline from the list. |

`field` is optional so a column can render a value no row property holds — a
join, something computed. Such a column must set both `render` and `label`,
never sorts or filters, and is skipped by export; see
[Joined or computed values](/docs/customization/column-renderers#joined-or-computed-values).

### ColumnFormat

Declarative cell rendering — reach for this before writing a `render` function.

```ts excerpt
type ColumnFormat =
  | "badge"
  | "money"
  | "number"
  | { kind: "badge"; tones?: Record<string, Tone> }
  | { kind: "money"; currency?: string; scale?: number };
```

`Tone` is `"default" | "accent" | "ok" | "warn" | "err" | "info" | "muted"`.
The object form of `badge` maps each cell value to a tone:

```ts excerpt
{ field: "status", format: { kind: "badge", tones: { paid: "ok", failed: "err" } } }
```

## FieldDef

An entry in `create.fields`, `update.fields`, a `DetailTab`, or an action's
`form`. Every rule here is enforced on the server, not just in the UI.

**FieldDef**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string & keyof Row` | yes | Row property this field writes. |
| `label` | `string` | no | Field label. Defaults to a humanized `name`. |
| `help` | `string` | no | Hint rendered under the control. |
| `placeholder` | `string` | no | Placeholder text for empty inputs. |
| `type` | `FieldType` | no | Which control to render. Omitted, a declared field renders as `"text"` —
or `"reference"` when `reference` is set. The adapter's column type is
consulted only by the fully generated form a resource gets when it declares
no `create.fields` / `update.fields` at all, and only maps number, boolean,
date and json; every other column type renders as text there too. |
| `options` | `function \| array` | no | Choices for `select` / `multiselect` / `radio`. A function resolves per request. |
| `reference` | `object` | no | Turn the field into a searchable picker over another resource. |
| `required` | `boolean` | no | Mark the field required in the form. Server-side validation still comes from `schema`. |
| `readOnly` | `function \| boolean` | no | Render non-editable. Enforced server-side, not just in the UI. |
| `hidden` | `function \| boolean` | no | Leave the field out of the form entirely. |
| `requireRole` | `function \| array \| string` | no | Restrict who may see and write this field. Enforced on every write route. |
| `validate` | `function \| object` | no | Per-field validation, run server-side after the resource schema. |
| `defaultValue` | `unknown` | no | Value used when creating, for keys the operator left empty. |
| `span` | `12 \| 6 \| 4 \| 3 \| 2 \| 1` | no | Width in a 12-column form grid. Defaults to full width. |
| `group` | `string` | no | Fieldset heading this field is grouped under. |

### FieldType

```ts excerpt
type FieldType =
  | "text" | "textarea" | "number" | "email" | "password" | "url"
  | "date" | "datetime" | "time"
  | "boolean" | "switch" | "checkbox"
  | "select" | "multiselect" | "radio"
  | "json" | "markdown" | "tags" | "reference" | "hidden" | "color";
```

Omit it on a declared field and the control is `"text"` — or `"reference"`
when the field sets `reference`. The adapter's `ColumnMeta.type` is consulted
only by the fully generated form a resource gets when it declares **no**
`create.fields` / `update.fields` at all, and even there it only maps
`number`, `boolean`, `date` and `json`; every other column type renders as
text. So `enum`, `array` and `reference` columns need an explicit `type` (plus
`options` / `reference`) to get a richer control.

## FilterDef

An entry in `filters`. A bare string is **always** a `text` filter — there is
no inference from the column's type, so declare a `FilterDef` with an explicit
`type` for a select, a date range, or anything else.

**FilterDef**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `field` | `string & keyof Row` | yes | Row property this filter narrows. |
| `label` | `string` | no | Control label. Defaults to a humanized `field`. |
| `type` | `FilterType` | yes | Which control to render, and how the value is encoded in the URL. |
| `options` | `function \| array` | no | Choices for `select` / `multiselect`. A function is resolved per request. |
| `defaultValue` | `unknown` | no | Applied until the operator picks a value of their own. |
| `hidden` | `boolean` | no | Apply the filter without showing a control for it. |

### FilterType

```ts excerpt
type FilterType =
  | "text" | "select" | "multiselect"
  | "daterange" | "numeric-range" | "boolean" | "tag";
```

`daterange` and `numeric-range` reach your adapter as a `FilterRangeValue`;
`multiselect` as a `FilterInValue`. Both are documented under
[Contexts](/docs/reference/contexts#filter-values).

## DetailTab

A tab on the full-page detail view at `/<basePath>/<resource>/<id>`.

**DetailTab**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | `string` | yes | Stable identifier, used as the tab's URL fragment. |
| `label` | `string` | yes | Tab label. |
| `hidden` | `function` | no | Hide the tab for rows that should not show it. |
| `fields` | `"*" \| array` | no | Field list to render as a key/value view. `"*"` shows every column. |
| `resource` | `string` | no | Render rows of a related resource instead of fields. |
| `filter` | `function` | no | Filter applied to `resource`, derived from the row being viewed. |
| `render` | `function` | no | Render arbitrary content instead of fields or a related resource. |

## SelectOption

```ts excerpt
type SelectOption = { label: string; value: string | number | boolean };
```

Wherever `options` is accepted, you may pass `string[]`, `SelectOption[]`, or a
function resolved per request.

## ListResult

What a `list` returns, from the adapter through to the pagination control.

**ListResult** — One page of rows, as returned by `Adapter.list`.
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `rows` | `array` | yes |  |
| `total` | `number` | yes | Total matching rows across every page — drives the pagination control. |
| `page` | `number` | yes | 1-based index of the page in `rows`. |
| `pageSize` | `number` | yes |  |

## ResourceConfig

What `resource()` returns. You pass it to `defineAdmin`; you rarely read it.

**ResourceConfig**
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `__kind` | `"resource"` | yes |  |
| `ref` | `Ref` | yes |  |
| `options` | `Options & object` | yes |  |
