Skip to content

filter_not[field]=null silently ignored for nested growth/specification fields #167

Description

@pjacques

The filter_not[]=null parameter on GET /api/v1/species (and /api/v1/plants) works correctly for top-level scalar fields like light, but is silently ignored for nested numeric/array fields such as spread.cm, row_spacing.cm, average_height.cm, maximum_height.cm, and bloom_months. Instead of narrowing results or returning an error, the API returns the full unfiltered result set, with no indication the filter was dropped.

Steps to reproduce

Baseline — unfiltered total
GET https://trefle.io/api/v1/species?token={token}&page=1&page_size=1
→ meta.total: 489358

Working filter, for contrast — light is a top-level scalar
GET https://trefle.io/api/v1/species?token={token}&filter_not[light]=null&page=1&page_size=1
→ meta.total: 3542 (correctly narrowed)

Every variant below returns meta.total: 489358 — i.e. completely unfiltered,
identical to the baseline with no filter applied at all
GET .../species?token={token}&filter_not[spread]=null
GET .../species?token={token}&filter_not[spread.cm]=null
GET .../species?token={token}&filter_not[spread][cm]=null
GET .../species?token={token}&filter_not[growth.spread.cm]=null
GET .../species?token={token}&filter_not[growth][spread][cm]=null
GET .../species?token={token}&filter_not[height]=null
GET .../species?token={token}&filter_not[average_height]=null
GET .../species?token={token}&filter_not[maximum_height]=null
GET .../species?token={token}&filter_not[bloom_months]=null

Expected behavior

Either:

  • The filter narrows results to species where the nested field is non-null (matching the behavior of filter_not[light]=null), or
  • The API returns an error/400 for an unsupported filter key, so callers can tell the parameter wasn't understood.

Actual behavior

The parameter is accepted (200 OK) but has no effect whatsoever — meta.total and the returned records are byte-for-byte identical to making the same request with no filter at all. This makes it impossible to discover, for example, "species with recorded row_spacing/spread data" without paging through the entire ~489k-record catalogue and checking each species' detail record client-side.

Example of the resulting confusion

A request like filter_not[spread]=null still returns records where the field is in fact null:

"row_spacing": { "cm": null },
"spread": { "cm": null },

Environment

Endpoint: GET /api/v1/species (confirmed also affected on GET /api/v1/plants)
Tested: 2026-07 against the live production API
Working counter-example: filter_not[light]=null (top-level scalar) behaves correctly

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions