# Custom fields

> Extend contacts with typed, discoverable attributes.

## Definitions and values

A custom field adds a structured attribute without changing the contact schema. Definitions live in `field_definitions`; values live in each record's JSONB `custom_fields` object. Definitions are workspace-specific and apply to contacts, organisations, projects, and tasks. Use `fields_list` with an optional `entity` filter to discover the appropriate definitions.

An administrator registers a key and its meaning once. Agents discover it with `fields_list`, then populate it. Unknown keys and values with the wrong type are rejected. Agents cannot create or change definitions.

## Discover available fields

During beta, field definitions are managed by Orbit administrators. Self-service definition creation is not available in the account interface or MCP yet. Agents must use existing definitions; they cannot invent new keys on the fly.

Call `fields_list` with your `workspace_id` and an optional `entity` filter (`contact`, `organisation`, `project` or `task`) to inspect available keys, descriptions and types. The same key can have different definitions for different entity types.

## Supported types

| Type | Accepted values |
| --- | --- |
| `text` | A string of up to 2,048 characters. |
| `number` | A JSON number, not a numeric string. |
| `boolean` | JSON `true` or `false`, not text or integers. |
| `date` | A valid `YYYY-MM-DD` date. |
| `datetime` | ISO-style date/time with seconds and an explicit `Z` or offset. |
| `string_list` | Up to 20 strings, each up to 255 characters. |

Each record supports at most 30 populated keys and 16 KiB of custom-field JSON. Null is not a valid field value or a deletion shortcut.

## Patch values explicitly

If `preferred_contact_method` exists as a text field for contacts, call `contacts_update`:

```json
{
  "workspace_id": "01900000-0000-7000-8000-000000000010",
  "id": "01900000-0000-7000-8000-000000000001",
  "expected_revision": 1,
  "idempotency_key": "contact-preference-001",
  "changes": {"custom_fields": {"preferred_contact_method": "email"}}
}
```

Only supplied custom-field keys change. Other custom fields remain intact. An empty object does not clear them. To remove a value, use `remove_custom_fields` on `contacts_update`:

```json
{
  "workspace_id": "01900000-0000-7000-8000-000000000010",
  "id": "01900000-0000-7000-8000-000000000001",
  "expected_revision": 2,
  "idempotency_key": "contact-preference-remove-001",
  "remove_custom_fields": ["preferred_contact_method"]
}
```

A key cannot be set and removed in one request. All keys must be registered, including explicit removals. Removing a value preserves the definition.

## Filter contacts

```json
{"workspace_id": "01900000-0000-7000-8000-000000000010", "custom_fields": {"preferred_contact_method": "email"}}
```

Pass this to `contacts_search`, `organisations_search`, `projects_search`, or `tasks_search`, using a definition registered for that entity. Filters use typed equality and all supplied filters must match. Date/time custom values retain their supplied offset; equality compares the stored representation, not equivalent instants in different offsets. Missing values do not match. Only scalar fields support equality filters; string-list matching, nested queries, arbitrary query operators, and custom aggregations are deferred.
