Column definitions control table cells and are reused by drawer/detail field rendering. Use a built-in format when it fits; reach for `render` only when the value needs application-specific presentation.

## Use built-in formatting

```ts excerpt
columns: [
  "email",
  { field: "status", format: { kind: "badge", tones: { active: "ok", blocked: "danger" } } },
  { field: "balance", label: "Balance", format: { kind: "money", currency: "USD" }, align: "right" },
]
```

Built-in badge, money, and number formats stay consistent across the admin and preserve the field's normal sort/export behavior.

## Render a custom server cell

```tsx excerpt
columns: [
  {
    field: "customerId",
    label: "Customer",
    render: async (order, ctx) => {
      const customer = await findCustomer(ctx.db, order.customerId);
      return <a href={`/admin/customers/${customer.id}`}>{customer.email}</a>;
    },
  },
]
```

`render` runs on the server with the row and request context. The function and database client never cross the browser boundary. Return only UI intended for the operator; the row still must be protected by resource access and scope.

For foreign keys, prefer a declarative `reference` when the target is another registered resource. It provides a consistent link and label lookup without a per-cell query.

## Add an interactive client cell

A server renderer may return a client component:

```tsx excerpt
render: (row) => <CopyId value={String(row.id)} />
```

Keep the client props small and serializable. The client component may manage interaction, but it should call an authorized server route for mutations rather than receiving server services as props.

## Joined or computed values

Omit `field` when the cell is derived entirely from the row:

```tsx excerpt
{
  label: "Customer",
  render: (order) => `${order.firstName} ${order.lastName}`,
}
```

A computed-only column has no adapter field. FlowPanel cannot automatically sort, filter, inline-edit, or include it in field-based export. If those operations matter, expose the value through the data model or a custom adapter/query surface.

## Check all consumers

Before shipping a custom column, verify the list, drawer/detail view, export, empty value, long value, and mobile width. `hidden` affects the table but can leave the field available to another declared surface; `expose` controls additional values that may cross the generated boundary.

Use a [theme slot](/docs/customization/theme-slots) when the same primitive needs to change everywhere, or a [custom page](/docs/reference/widgets#dashboards) when the entire row experience no longer fits a table.

See [Resources reference](/docs/reference/resources#columnformat) for generated column types.
