---
title: Profile fields
description: Store custom fields — a role, a bio, preferences — on every user.
---

Beyond identity, a project can define custom fields stored on every user: a `bio`, a `role`,
a set of interests. You define them in the dashboard or via the MCP. Your app reads and writes their values
through the SDK.

## Defining a field

Go to **Authentication → Profile fields** and add a field. Each one has:

| Setting                  | Description                                       |
| ------------------------ | ------------------------------------------------- |
| **Name**                 | The key you'll use in code, e.g. `role`           |
| **Description**          | A note for whoever manages users in the dashboard |
| **Type**                 | Text, Single choice, or Multiple choice           |
| **Options**              | For the choice types — one per line               |
| **Visible to the user**  | Whether the user can see the field at all         |
| **Editable by the user** | Whether the user can change it themselves         |

### Types

The following TypeScript types will be returned depending on the type of the profile field:

| Type            | Value in code                 |
| --------------- | ----------------------------- |
| Text            | `string \| null`               |
| Single choice   | `string \| null`               |
| Multiple choice | `string[] \| null`             |

### Visibility and editability

The visibility and editability properties of a field define the following:

**Visible**:

- `true` - This field will be returned in the `getUser({ profile: true })` response.
- `false` - This field will not be returned when getting the profile.

**Editable:**

- `true` - This field can be edited by the user themselves via the `updateProfile()` method.
- `false` - This field can only be edited from the dashboard. Useful for fields like a `role`.

Editable implies visible, so a field that isn't visible can't be user-editable.

## Reading the profile

Ask for the profile alongside the user — one round trip:

```ts
const me = await client.auth.getUser({ profile: true });
me?.profile.bio; // "…"
me?.profile.role; // "member"
```

## Writing the profile

```ts
await client.auth.updateProfile({ bio: 'Hello' });
await client.auth.updateProfile({ bio: null }); // null clears a field
```

A user can only update their own profile, and only fields marked editable. Writing a
profile field that was marked as not editable is rejected.

## Typing the profile

You can pass your field shape as a type argument:

```ts
type Profile = {
  bio: string;
  role: 'admin' | 'member';
  interests: string[];
};

const me = await client.auth.getUser<Profile>({ profile: true });
me?.profile.role; // 'admin' | 'member' | null | undefined
me?.profile.xyz; // compile error — not a declared field
```

> [!NOTE]
> Every field comes back **optional and nullable** — absent if not visible to you, `null` if
> visible but unset. Declare the plain shape and the SDK adds `| null | undefined` for you.

Field values must extend `string | string[] | null`.

## Managing fields

Add, change or remove fields in the dashboard, or through the
[MCP server](/getting-started/mcp-server/) — handy when an assistant is writing the code that
uses them. The SDK reads and writes **values**, never definitions.
