# Using Custom Profile Fields in Logto for Extended User Data

> Extend user profiles in Logto with custom fields defined via the admin console or API. Collect extended user data easily during sign-up.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-04

---

**Logto allows you to extend user profiles with arbitrarily-named, typed custom fields that are defined in the admin console or via the Admin API and automatically collected during sign-up.**

Logto is an open-source identity management solution developed by the logto-io/logto repository. Using custom profile fields in Logto for extended user data enables you to capture domain-specific information beyond standard attributes like email and name, with full type safety and validation built into the sign-up flow.

## Understanding Custom Profile Field Types

Logto supports nine distinct field types defined in [`packages/schemas/src/types/custom-profile-fields.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/custom-profile-fields.ts). Each type includes specific configuration options and validation rules:

- **TextProfileField** – Free-form text input with optional placeholder text
- **NumberProfileField** – Numeric values with min/max constraints
- **DateProfileField** – Date pickers for birthdates or other temporal data
- **CheckboxProfileField** – Boolean toggle switches
- **SelectProfileField** – Dropdown menus with predefined options
- **UrlProfileField** – URL validation for website or portfolio links
- **RegexProfileField** – Custom regular expression pattern matching
- **AddressProfileField** – Structured address data with sub-parts (street, city, postal code), validated against `userProfileAddressKeys` using `fieldPartGuard`
- **FullnameProfileField** – Composite name fields

All type definitions use Zod guards compiled with `satisfies ToZodObject<...>`, ensuring runtime validation matches TypeScript type inference throughout the codebase.

## Architectural Overview

### Field Definitions and Schema

Custom fields are stored in the `custom_profile_fields` table and managed through layered abstractions. The `custom-profile-fields` library in [`packages/core/src/libraries/custom-profile-fields/index.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/custom-profile-fields/index.ts) provides CRUD operations, while [`packages/core/src/queries/custom-profile-fields.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/queries/custom-profile-fields.ts) handles direct database access for inserts, updates, deletes, and reordering operations.

When you create a field via `POST /api/custom-profile-fields`, the request body validates against Zod guards (`customProfileFieldGuard`, `textProfileFieldGuard`, etc.) before persistence.

### Sign-in Experience Integration

The `FullSignInExperience` type in [`packages/schemas/src/types/sign-in-experience.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/sign-in-experience.ts) (lines 55-62) maintains two arrays:

1. `customProfileFields` – Fields displayed during sign-up
2. `customProfileFieldCatalog` – Complete field set used by the Account Center and profile pages

The utility function `resolveSignUpCustomProfileFields` in [`packages/core/src/libraries/custom-profile-fields/utils.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/custom-profile-fields/utils.ts) computes the ordered list of fields to collect during registration.

### Database and Query Layer

The query layer in [`packages/core/src/queries/custom-profile-fields.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/queries/custom-profile-fields.ts) provides methods for:
- Batch ordering via `displayOrder` column updates
- Field metadata retrieval
- Row-level operations for the `custom_profile_fields` table

## Managing Custom Profile Fields via the Admin API

The REST endpoints defined in [`packages/core/src/routes/custom-profile-fields.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/custom-profile-fields.ts) expose full CRUD capabilities. All examples assume an admin access token stored in `ADMIN_TOKEN` and a base URL `https://logto.example.com/api`.

### Creating and Updating Fields

To create a required text field for company information:

```typescript
await fetch(`${BASE_URL}/custom-profile-fields`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${ADMIN_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'company',
    label: 'Company',
    type: 'Text',
    required: true,
    config: { placeholder: 'Acme Corp.' },
  }),
});

```

To modify a field (making it optional):

```typescript
await fetch(`${BASE_URL}/custom-profile-fields/company`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${ADMIN_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ required: false }),
});

```

To retrieve the complete catalog:

```typescript
const catalog = await fetch(`${BASE_URL}/custom-profile-fields`, {
  headers: { Authorization: `Bearer ${ADMIN_TOKEN}` },
}).then(res => res.json());
// Returns CustomProfileField[]

```

### Ordering Fields for Sign-up

Control the display order on the sign-up page using the `sie-order` endpoint:

```typescript
await fetch(`${BASE_URL}/custom-profile-fields/properties/sie-order`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${ADMIN_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ 
    fieldNames: ['company', 'nickname', 'birthdate'] 
  }),
});

```

The order array determines the `displayOrder` column values in the database.

### Deleting Fields

Remove a field permanently:

```typescript
await fetch(`${BASE_URL}/custom-profile-fields/company`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${ADMIN_TOKEN}` },
});

```

## Validating Custom Fields During Registration

During sign-up, the validation flow in [`packages/core/src/routes/experience/classes/libraries/profile-validator.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/experience/classes/libraries/profile-validator.ts) ensures data integrity:

1. The client fetches `FullSignInExperience` from `/api/sign-in-experience`
2. `resolveSignUpCustomProfileFields` extracts the ordered list of required fields
3. The validator checks that all `required` fields contain values before allowing registration completion
4. Valid data is stored in the user's `customData` property as `{ <fieldName>: <value>, ... }`

Reserved keys such as `email` and `phone` cannot be used for custom field names, as enforced by validation logic around line 298 in the custom profile fields implementation.

## Integration Testing

The end-to-end test suite in [`packages/integration-tests/src/tests/api/custom-profile-fields.test.ts`](https://github.com/logto-io/logto/blob/main/packages/integration-tests/src/tests/api/custom-profile-fields.test.ts) demonstrates field creation, ordering, and sign-up validation workflows, providing reference implementations for custom field lifecycle management.

## Summary

- **Nine field types** (Text, Number, Date, Checkbox, Select, URL, Regex, Address, Fullname) provide flexible data collection options
- **Type-safe validation** via Zod guards ensures data integrity across the TypeScript codebase
- **Admin API endpoints** (`/custom-profile-fields/*`) support full CRUD operations and field ordering
- **Sign-up integration** automatically renders required fields and validates input before registration completion
- **Data storage** occurs in `customData` object within the user profile, separate from standard identity claims

## Frequently Asked Questions

### What reserved keys cannot be used for custom profile fields?

Keys such as `email`, `phone`, and other standard user profile attributes are reserved and cannot be used as custom field names. The validation logic in the custom profile fields module (around line 298 in the source) explicitly blocks these identifiers to prevent conflicts with built-in user properties.

### How does Logto validate address fields with multiple parts?

The `AddressProfileField` type uses `fieldPartGuard` to validate sub-components like street, city, and postal code against the `userProfileAddressKeys` definition. Each address part undergoes individual validation while maintaining the overall field structure, allowing complex address data to be collected as a single custom field.

### Can I make custom fields optional for some users but required for others?

No. The `required` flag is set at the field definition level in the `custom_profile_fields` table and applies universally to all users during sign-up. However, you can update this flag dynamically via the Admin API (`PUT /custom-profile-fields/{name}`) if your business rules change, affecting all subsequent registrations.

### Where does Logto store the values collected from custom profile fields?

Collected values are stored in the `customData` property of the user object as key-value pairs matching the field names. This structure keeps extended user data separate from standard identity claims (email, phone, username) while remaining accessible through the standard user profile API.