Filtering/querying (deep) relationships - a proposal for a syntax

JSON:API currently lacks a standard for filtering/querying data included through relationships, as opposed to GraphQL which has this firmly specified.

Consider the following API:

  • GET /authors to get all resources of type author
  • Resource author has relationship articles which is to-many article
  • Resource article has attribute isDraft: boolean

Now consider the following request:

  • “Give me all authors who have draft articles, and include their draft articles”.

There are two filters here:

  • The filter on the author list (filtering on a related resource’s attribute)
  • The filter on the included articles

Here is a syntax that might be able to confer the necessary distinction:

GET /authors

The meaning of the two filters would be:

  • filter[articles.isDraft]=true means "only return authors who have articles with isDraft=true"
  • filter[articles][isDraft]=true means "for any included articles from the articles relationship, only return those with isDraft=true"

In other words:

  • The first (of two) pair of brackets indicate what to filter (if not present, filter top-level data)
  • The second (or only) pair of brackets indicate how to filter, using dot notation for filtering on related resources

As far as I can see, this syntax is general enough to be nested artibrarily on any level. For example, filter[articles.comments][]=John&include=articles.comments means "for each article, include only those comments whose author has name=John"

Does this make sense? Could this syntax be a clear, general, and consistent way to support filters on nested relationships?

Also, how will you get all draft articles that were created two months ago?

This proposal is not needed for that. I do it like this (assuming you meant at least two months ago):

GET /articles?filter[isDraft]=true&filter[createdAt][le]=2020-07-01

Where le indicates <=.

Granted, JSON:API does not have a standard syntax for operators like [le] above, but it’s a fairly trivial addition that does not break any part of the spec and which I’ve seen several others use.

Some operators I’ve used:

  • [lt] (<)
  • [gt] (>)
  • [le] (<=)
  • [ge] (>=)
  • [matches] (fuzzy string matching)
1 Like

Yeah! You’re correct, I meant at least two months ago.

I like that strategy, it simple and easy to understand. I think I might use some other operators such as:

  • [since]
  • [until]
  • [between]


GET /articles?filter[isDraft]=true&filter[createdAt][between]=2020-07-01,2020-07-08
GET /articles?filter[isDraft]=true&filter[createdAt][since]=2020-07-01
GET /articles?filter[isDraft]=true&filter[createdAt][since]=2020-07-01&filter[createdAt][until]=2020-07-08

Thanks for you quick reply.

That works, too. Note that if you have since/until (or le/ge), then between is superfluous. (You may still want to include it for the sake of API ergonomics, but I would contest that APIs are generally made for machines, not humans, and that in this case the ergonomic difference is negligible and comes at the cost of clearly separated/orthogonal filters. YMMV.)