# Custom Field Filtering

> **Experimental — this may change or break at any time.** Custom field
filtering is not a stable part of the API. Its syntax, the data types each
filter supports and its error responses can change without warning or a
version bump, and integrations built on it may break unexpectedly as a
result. Do not depend on it in production, and expect to update your code
without notice.


Custom fields are the fields your workspace defines on top of the built-in ones. Endpoints that support custom field filtering take a `customFields` query parameter, naming the field after a dot:

```
?customFields.tier=gold
```

The syntax below is the same wherever the filter is accepted. Which endpoints accept it is stated in the API reference — check the endpoint's parameters rather than assuming, as the set of endpoints supporting it may grow.

Every key is the `key` of a custom field returned by [listCustomFields](/v1/openapi/customfields/listcustomfields). Requesting a key that your workspace has not defined returns `400 Bad Request`, so call that endpoint first if you are unsure what is available:

```shell curl
curl -i -X GET \
  https://api.hook.co/v1/organizations/my-organization-id/custom-fields
```

## Matching values

Repeat the parameter to match any one of several values:

```
?customFields.tier=gold&customFields.tier=silver
```

Fields holding a boolean accept `true` or `false`. Any other value returns `400 Bad Request`:

```
?customFields.is_paying=true
```

## Matching a range

Fields holding a number or a date are filtered by a range, naming the bound in brackets. These are filtered this way only — they do not match exact values:

```
?customFields.health_score[from]=50&customFields.health_score[to]=100
```

Both bounds are required, and both are inclusive. Supplying only one returns `400 Bad Request`.

Date bounds are `YYYY-MM-DD`, or a full ISO 8601 datetime in UTC — the `Z` suffix is required. A datetime carrying a timezone offset, such as `2026-01-01T09:30:00+01:00`, or none at all is not accepted:

```
?customFields.signed_date[from]=2026-01-01&customFields.signed_date[to]=2026-12-31
?customFields.signed_date[from]=2026-01-01T09:30:00Z&customFields.signed_date[to]=2026-12-31T23:59:59Z
```

A date field holds a calendar date, so a datetime bound is narrowed to the day it names and the time carries no meaning. The two examples above match the same records.

## Supported data types

Which filter a custom field accepts depends on its `dataType`, as returned by `listCustomFields`:

| `dataType` | Matching values | Matching a range |
|  --- | --- | --- |
| `string`, `array` | Yes | No |
| `boolean` | Yes, `true` or `false` | No |
| `date` | No | Yes |
| `date_array` | No | Yes |
| `integer`, `float`, `currency`, `percentage` | No | Yes |


A `date_array` holds a list of dates and is filtered by a range alone. It matches when any one of its entries falls within the bounds.

Using a filter a field does not support returns `400 Bad Request`, naming the data types that do support it.

## Errors

Every problem with a custom field filter returns `400 Bad Request` with a message describing it, rather than silently returning unfiltered results:

| Cause | Example |
|  --- | --- |
| The custom field is not defined in your workspace | `?customFields.not_a_field=1` |
| The field does not support that filter | `?customFields.tier[from]=a`, `?customFields.signed_date=2026-01-01` |
| Only one bound of a range was supplied | `?customFields.health_score[from]=50` |
| A bound is not a valid date or number | `?customFields.health_score[from]=high`, `?customFields.signed_date[from]=2026-01-01T09:30:00+01:00` |
| A boolean value is not `true` or `false` | `?customFields.is_paying=yes` |