---
title: Extend field roles
description: Convert and display a field value through a custom role.
---

A field kind defines the value, such as text or a number. A role defines how
the server prepares that value for a use. A variant selects an option within
the role. Backlit includes the `display` and `input` roles.

Add a custom role when several blocks need the same data conversion. Use a
variant or client styling for a display change that does not change the data.
This example adds a `summary` role for text and shortens long notes on the server.

## Protocol and server

Augment `FieldRoleRegistry` in `@backlit/contract`. Map each supported field kind
to its variants. Use `never` for a supported kind with no variants. A missing
kind does not support the role.

Register a complete role map with `remappers.registerRole`. The example calls
the extension directly to keep the resource example self-contained. In an app,
pass the extension to `defineApp({ extensions: [summary], areas })`.

<BacklitExample path="extensions/summary" view="list" />

Import the protocol module from both server and client extension modules.
`FieldRemapper` and `BacklitExtension` come from `@backlit/sdk/types`.
A remapper returns the value sent to the client. It can call `report(reason)`
to omit an invalid value. The client does not receive the original record.

## Client

Supply a matching field primitive through the client extension. The key joins
the field kind and the capitalized role, so this role uses `textSummary`.
Use the same extension name and version on both sides.

```tsx title="client.tsx"
import './protocol.ts'
import type { TextField } from '@backlit/contract'
import type {
  BacklitClientExtension,
  FieldPrimitiveProps,
  FieldRolePrimitiveMap,
} from '@backlit/ui/types'

function TextSummary({ value }: FieldPrimitiveProps<TextField>) {
  return <span>{String(value ?? '')}</span>
}

const primitives = {
  textSummary: TextSummary,
} satisfies FieldRolePrimitiveMap<'summary'>

export const summaryClient = {
  name: 'summary',
  version: '1.0.0',
  primitives,
} satisfies BacklitClientExtension
```

Pass `summaryClient` in the `extensions` prop of `BacklitApp`. The app host must
also supply its link component, navigation function, search adapter, and children.
The client compares the extension identities reported by the server.

## Select a role

Use `setRoles({ fieldName: 'summary' })` on a table, datalist, lookup, form field
block, or repeater. A field without a selection uses the block default:
`display` for data blocks, and `input` for form fields and repeaters.
Role selections travel beside the fields and data.

A role map must contain every kind that its declaration supports. Use
`registerRemappers` only to replace entries in a registered role. A missing
runtime handler reports a defect and uses the fallback role. The client also
has a fallback when a matching primitive is missing.

Relations delegate conversion to their declared display field. Do not register
a `has` remapper to calculate a relation count. Fetch the count as a number and
show it through a number field. Groups also use their members to convert values.
See [Extensions](/advanced/extensions) for registry scope and builder contracts.
