Filtering

It is possible to define filters for Django types, which will be converted into .filter(...) queries for the ORM:

types.py
import strawberry_django
from strawberry import auto
@strawberry_django.filter(models.Fruit)
class FruitFilter:
id: auto
name: auto
@strawberry_django.type(models.Fruit, filters=FruitFilter)
class Fruit:
...

Tip

In most cases filter fields should have Optional annotations and default value strawberry.UNSET like so: foo: Optional[SomeType] = strawberry.UNSET Above auto annotation is wrapped in Optional automatically. UNSET is automatically used for fields without field or with strawberry_django.filter_field .

The code above would generate following schema:

schema.graphql
input FruitFilter {
id: ID
name: String
AND: FruitFilter
OR: FruitFilter
NOT: FruitFilter
DISTINCT: Boolean
}

Tip

If you are using the relay integration and working with types inheriting from relay.Node and GlobalID for identifying objects, you might want to set MAP_AUTO_ID_AS_GLOBAL_ID=True in your strawberry django settings to make sure auto fields gets mapped to GlobalID on types and filters.

AND, OR, NOT, DISTINCT …

To every filter AND , OR , NOT & DISTINCT fields are added to allow more complex filtering

{
fruits(
filters: {
name: "kebab"
OR: {
name: "raspberry"
}
}
) { ... }
}

Lookups

Lookups can be added to all fields with lookups=True , which will add more options to resolve each type. For example:

types.py
@strawberry_django.filter(models.Fruit, lookups=True)
class FruitFilter:
id: auto
name: auto

The code above would generate the following schema:

schema.graphql
input IDBaseFilterLookup {
exact: ID
isNull: Boolean
inList: [String!]
}
input StrFilterLookup {
exact: ID
isNull: Boolean
inList: [String!]
iExact: String
contains: String
iContains: String
startsWith: String
iStartsWith: String
endsWith: String
iEndsWith: String
regex: String
iRegex: String
}
input FruitFilter {
id: IDFilterLookup
name: StrFilterLookup
AND: FruitFilter
OR: FruitFilter
NOT: FruitFilter
DISTINCT: Boolean
}

Single-field lookup can be annotated with the FilterLookup generic type.

types.py
from strawberry_django import FilterLookup
@strawberry_django.filter(models.Fruit)
class FruitFilter:
name: FilterLookup[str]

Filtering over relationships

types.py
@strawberry_django.filter(models.Color)
class ColorFilter:
id: auto
name: auto
@strawberry_django.filter(models.Fruit)
class FruitFilter:
id: auto
name: auto
color: ColorFilter | None

The code above would generate following schema:

schema.graphql
input ColorFilter {
id: ID
name: String
AND: ColorFilter
OR: ColorFilter
NOT: ColorFilter
}
input FruitFilter {
id: ID
name: String
color: ColorFilter
AND: FruitFilter
OR: FruitFilter
NOT: FruitFilter
}

Custom filter methods

You can define custom filter method by defining your own resolver.

types.py
@strawberry_django.filter(models.Fruit)
class FruitFilter:
name: auto
last_name: auto
@strawberry_django.filter_field
def simple(self, value: str, prefix) -> Q:
return Q(**{f"{prefix}name": value})
@strawberry_django.filter_field
def full_name(
self,
queryset: QuerySet,
value: str,
prefix: str
) -> tuple[QuerySet, Q]:
queryset = queryset.alias(
_fullname=Concat(
f"{prefix}name", Value(" "), f"{prefix}last_name"
)
)
return queryset, Q(**{"_fullname": value})
@strawberry_django.filter_field
def full_name_lookups(
self,
info: Info,
queryset: QuerySet,
value: strawberry_django.FilterLookup[str],
prefix: str
) -> tuple[QuerySet, Q]:
queryset = queryset.alias(
_fullname=Concat(
f"{prefix}name", Value(" "), f"{prefix}last_name"
)
)
return strawberry_django.process_filters(
filters=value,
queryset=queryset,
info=info,
prefix=f"{prefix}_fullname"
)

Warning

It is discouraged to use queryset.filter() directly. When using more complex filtering via NOT , OR & AND this might lead to undesired behaviour.

[!TIP]

process_filters

As seen above strawberry_django.process_filters function is exposed and can be reused in custom methods. Above it’s used to resolve fields lookups

null values

By default null value is ignored for all filters & lookups. This applies to custom filter methods as well. Those won’t even be called (you don’t have to check for None ). This can be modified using strawberry_django.filter_field(filter_none=True)

This also means that built in exact & iExact lookups cannot be used to filter for None and isNull have to be used explicitly.

value resolution

  • value parameter of type relay.GlobalID is resolved to its node_id attribute
  • value parameter of type Enum is resolved to is’s value
  • above types are converted in lists as well

resolution can modified via strawberry_django.filter_field(resolve_value=...)

  • True - always resolve
  • False - never resolve
  • UNSET (default) - resolves for filters without custom method only

The code above generates the following schema:

schema.graphql
input FruitFilter {
name: String
lastName: String
simple: str
fullName: str
fullNameLookups: StrFilterLookup
}

Resolver arguments

Why prefix?
@strawberry_django.filter(models.Fruit)
class FruitFilter:
name: auto
color: ColorFilter | None
@strawberry_django.filter(models.Color)
class ColorFilter:
@strawberry_django.filter_field
def name(self, value: str, prefix: str):
# prefix is "fruit_set__" if unused root object is filtered instead
if value:
return Q(name=value)
return Q()
{
fruits( filters: {color: name: "blue"} ) { ... }
}

Resolver return

For custom field methods two return values are supported

For default filter method only second variant is supported.

What about nulls?

By default null values are ignored. This can be toggled as such @strawberry_django.filter_field(filter_none=True)

Overriding the default filter method

Works similar to field filter method, but:

types.py
@strawberry_django.filter(models.Fruit)
class FruitFilter:
def ordered(
self,
value: int,
prefix: str,
queryset: QuerySet,
):
queryset = queryset.alias(
_ordered_num=Count(f"{prefix}orders__id")
)
return queryset, Q(**{f"{prefix}_ordered_num": value})
@strawberry_django.order_field
def filter(
self,
info: Info,
queryset: QuerySet,
prefix: str,
) -> tuple[QuerySet, list[Q]]:
queryset = queryset.filter(
... # Do some query modification
)
return strawberry_django.process_filters(
self,
info=info,
queryset=queryset,
prefix=prefix,
skip_object_order_method=True
)

Tip

As seen above strawberry_django.process_filters function is exposed and can be reused in custom methods. For filter method filter skip_object_order_method was used to avoid endless recursion.

Adding filters to types

All fields and CUD mutations inherit filters from the underlying type by default. So, if you have a field like this:

types.py
@strawberry_django.type(models.Fruit, filters=FruitFilter)
class Fruit:
...
@strawberry.type
class Query:
fruits: list[Fruit] = strawberry_django.field()

The fruits field will inherit the filters of the type in the same way as if it was passed to the field.

Adding filters directly into a field

Filters added into a field override the default filters of this type.

schema.py
@strawberry.type
class Query:
fruits: list[Fruit] = strawberry_django.field(filters=FruitFilter)

Generic Lookup reference

There is 7 already defined Generic Lookup strawberry.input classes importable from strawberry_django

BaseFilterLookup

RangeLookup

ComparisonFilterLookup

FilterLookup

DateFilterLookup

TimeFilterLookup

DatetimeFilterLookup

Legacy filtering

The previous version of filters can be enabled via USE_DEPRECATED_FILTERS

Warning

If USE_DEPRECATED_FILTERS is not set to True legacy custom filtering methods will be not be called.

When using legacy filters it is important to use legacy strawberry_django.filters.FilterLookup lookups as well. The correct version is applied for auto annotated filter field (given lookups=True being set). Mixing old and new lookups might lead to error DuplicatedTypeName: Type StrFilterLookup is defined multiple times in the schema .

While legacy filtering is enabled new filtering custom methods are fully functional including default filter method.

Migration process could be composed of these steps: