> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apivalk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Filtering

> Apivalk provides built-in support for route-level filtering, allowing clients to narrow down results by specifying conditions on fields.

<Tip>For a task-focused walkthrough (typed filters, controller usage, resource integration), see the [Add filtering](/how-to/filtering) how-to.</Tip>

## Route Configuration

To enable filtering for a specific route, you use the `filtering()` method on the `Route` object. This automatically handles the documentation and request parameter resolution.

```php theme={null}
use apivalk\apivalk\Router\Route\Filter\StringFilter;
use apivalk\apivalk\Router\Route\Filter\EnumFilter;
use apivalk\apivalk\Router\Route\Filter\IntegerFilter;
use apivalk\apivalk\Router\Route\Filter\FloatFilter;
use apivalk\apivalk\Router\Route\Filter\DateFilter;
use apivalk\apivalk\Router\Route\Filter\DateTimeFilter;
use apivalk\apivalk\Router\Route\Filter\BooleanFilter;
use apivalk\apivalk\Router\Route\Route;
use apivalk\apivalk\Documentation\Property\StringProperty;
use apivalk\apivalk\Documentation\Property\EnumProperty;
use apivalk\apivalk\Documentation\Property\IntegerProperty;
use apivalk\apivalk\Documentation\Property\FloatProperty;
use apivalk\apivalk\Documentation\Property\DateProperty;
use apivalk\apivalk\Documentation\Property\DateTimeProperty;
use apivalk\apivalk\Documentation\Property\BooleanProperty;

public static function getRoute(): Route
{
    return Route::get('/contracts')
        ->filtering([
            EnumFilter::equals(new EnumProperty('status', 'Filter by status', ['active', 'inactive'])),
            StringFilter::contains(new StringProperty('name', 'Filter by name (contains)')),
            FloatFilter::greaterThan(new FloatProperty('amount', 'Minimum amount', FloatProperty::FORMAT_DOUBLE)),
            IntegerFilter::lessThan(new IntegerProperty('id', 'Filter by ID (less than)')),
            DateFilter::greaterThan(new DateProperty('created_at', 'Filter by creation date (greater than)')),
            DateTimeFilter::lessThan(new DateTimeProperty('updated_at', 'Filter by update date (less than)')),
            BooleanFilter::equals(new BooleanProperty('is_active', 'Filter by active flag', false)),
        ]);
}
```

### Type-Safe Filtering

Apivalk provides specialized filter classes for different property types. Each filter is linked to its corresponding property type, ensuring that filter values are correctly type-cast when retrieved in your controller.

1. **StringFilter**: Linked to `StringProperty`. Supports `equals`, `in`, `like`, and `contains`.
2. **EnumFilter**: Linked to `EnumProperty`. Supports `equals` and `in`.
3. **IntegerFilter**: Linked to `IntegerProperty`. Supports `equals`, `in`, `greaterThan`, and `lessThan`. Returns `int` values.
4. **FloatFilter**: Linked to `FloatProperty`. Supports `equals`, `in`, `greaterThan`, and `lessThan`. Returns `float` values.
5. **DateFilter**: Linked to `DateProperty`. Supports `equals`, `in`, `greaterThan`, and `lessThan`. Returns `\DateTime` values.
6. **DateTimeFilter**: Linked to `DateTimeProperty`. Supports `equals`, `in`, `greaterThan`, and `lessThan`. Returns `\DateTime` values.
7. **ByteFilter**: Linked to `ByteProperty`. Supports `equals` and `in`.
8. **BinaryFilter**: Linked to `BinaryProperty`. Supports `equals` and `in`.
9. **BooleanFilter**: Linked to `BooleanProperty`. Supports `equals`. Returns `bool` values.

Benefits:

* **OpenAPI Accuracy**: The documentation reflects the correct data type (e.g., `number`, `integer`, `string`).
* **Automatic Casting**: `getValue()` returns the correct PHP type (`string`, `int`, `float`, or `\DateTime`), or `null` if the filter was not provided by the client.
* **Raw Access**: `getRawValue()` returns the original query string the client sent (e.g. `"2024-01-15T14:30:00+00:00"` for a `DateTimeFilter`), or `null` if the filter wasn't provided. Useful for audit logs, error messages that quote the user's literal input, or distinguishing "client supplied an unparseable value" (`getRawValue() !== null && getValue() === null`) from "client omitted the filter" (both `null`).
* **Detailed Documentation**: You can add custom descriptions, examples, and constraints to your filters.

```php theme={null}
IntegerFilter::equals(new IntegerProperty('id', 'Filter by ID (equals)', IntegerProperty::FORMAT_INT64))
```

### Supported Filter Types

#### StringFilter

* `StringFilter::equals(StringProperty $property)`: Exact match for a single string.
* `StringFilter::in(StringProperty $property)`: Match against a list of strings.
* `StringFilter::like(StringProperty $property)`: Partial string matching (e.g., `prefix%`).
* `StringFilter::contains(StringProperty $property)`: Match if a string contains another.

#### EnumFilter

* `EnumFilter::equals(EnumProperty $property)`: Exact match against allowed values.
* `EnumFilter::in(EnumProperty $property)`: Match against a list of allowed values.

#### IntegerFilter

* `IntegerFilter::equals(IntegerProperty $property)`: Exact integer match.
* `IntegerFilter::in(IntegerProperty $property)`: Match against a list of integers.
* `IntegerFilter::greaterThan(IntegerProperty $property)`: Integer comparison.
* `IntegerFilter::lessThan(IntegerProperty $property)`: Integer comparison.

#### FloatFilter

* `FloatFilter::equals(FloatProperty $property)`: Exact float match.
* `FloatFilter::in(FloatProperty $property)`: Match against a list of floats.
* `FloatFilter::greaterThan(FloatProperty $property)`: Float comparison.
* `FloatFilter::lessThan(FloatProperty $property)`: Float comparison.

#### DateFilter

* `DateFilter::equals(DateProperty $property)`: Exact date match.
* `DateFilter::in(DateProperty $property)`: Match against a list of dates.
* `DateFilter::greaterThan(DateProperty $property)`: Date comparison.
* `DateFilter::lessThan(DateProperty $property)`: Date comparison.

#### DateTimeFilter

* `DateTimeFilter::equals(DateTimeProperty $property)`: Exact date-time match.
* `DateTimeFilter::in(DateTimeProperty $property)`: Match against a list of date-times.
* `DateTimeFilter::greaterThan(DateTimeProperty $property)`: Date-time comparison.
* `DateTimeFilter::lessThan(DateTimeProperty $property)`: Date-time comparison.

#### ByteFilter

* `ByteFilter::equals(ByteProperty $property)`: Exact byte match.
* `ByteFilter::in(ByteProperty $property)`: Match against a list of byte values.

#### BinaryFilter

* `BinaryFilter::equals(BinaryProperty $property)`: Exact binary match.
* `BinaryFilter::in(BinaryProperty $property)`: Match against a list of binary values.

#### BooleanFilter

* `BooleanFilter::equals(BooleanProperty $property)`: Exact boolean match. Returns a `bool` value.

***

## Usage in Controller

When a route has filtering enabled, you can access the resolved filters from the request via the `filtering()` method.

```php theme={null}
public function __invoke(ApivalkRequestInterface $request): AbstractApivalkResponse
{
    $filters = $request->filtering();
    
    // Check if a specific filter was provided by the client
    if ($filters->has('status')) {
        $type = $filters->status->getType(); // for example contains, so you know you do str_contains for example
        $status = $filters->status->getValue();
        // apply to your query...
    }
    
    // Iterate over all provided filters
    foreach ($filters as $field => $filter) {
        $type = $filter->getType();
        $value = $filter->getValue();
        // ...
    }
    
    // ...
}
```

***

## Client-Side Usage

Apivalk supports two query string formats — both reach the same filter values in your controller.

### Flat notation

Each filter is a top-level query parameter. Simple and compact.

```
GET /contracts?status=active
GET /contracts?status=active&name=Acme
```

### Bracket notation

Filters are nested under a `filter` key. Useful when you want a clear namespace separation from other parameters (pagination, sorting, etc.), or when tooling like OpenAPI client generators constructs nested objects.

```
GET /contracts?filter[status]=active
GET /contracts?filter[status]=active&filter[name]=Acme
```

Both formats are equivalent — they produce the same `FilterBag` in your controller and can be mixed freely within a single request (flat takes precedence if both supply the same field).

***

## OpenAPI Documentation

When you configure filters on a route, Apivalk generates a single `filter` parameter in the OpenAPI spec using `style: deepObject`. Each declared filter appears as a named property inside its schema:

```yaml theme={null}
- name: filter
  in: query
  required: false
  style: deepObject
  explode: true
  schema:
    type: object
    properties:
      status:
        type: string
        description: "Filter by status"
      amount:
        type: number
        description: "Minimum amount (greater than)"
```

In Swagger UI, this renders as separate inputs labelled `filter[status]`, `filter[amount]`, etc. — matching the bracket notation clients should use at runtime. Both flat notation (`?status=active`) and bracket notation (`?filter[status]=active`) are accepted by the server regardless of which style is chosen.

Pass `flatFilters: true` to `OpenAPIGenerator` to switch to one flat query parameter per filter instead. See [OpenAPI Generator → Filter Documentation Style](/documentation/openapi-generator) for the full reference.
