Introduction
Strapi provides you with multiple ways to filter results on relation’s fields deeply. This article will cover the different ways to filter results using the following APIs:
- REST API,
- Entity Service API (Deprecated in Strapi 5),
- Query Engine API,
- GraphQL API, and
- Document Service API.
Prerequisites
Before starting this content, you need:
- Basic knowledge of JavaScript
- A basic understanding of Strapi — get started here
Use Case
The following example will be used to demonstrate the deep filtering capabilities of Strapi:
A collection type called Book
with the following fields:
title
authors
Another collection type called Author
with the following fields:
name
hobby
books
Books
can have multiple Authors
, and Authors
can write multiple Books
and have different Hobbies
.
The end goal is to filter Books
based on their Authors'
Hobbies
. Some sample data will help us explore different approaches practically. Consider four authors:
ID | NAME | HOBBY |
---|---|---|
1 | Author1 | play |
2 | Author2 | sing |
3 | Author3 | dance |
4 | Author4 | dance |
Author 1's hobby is to play
, Author 2's is to sing
, and Authors 3 and 4's hobby is to dance
.
Consider the following books:
ID | TITLE | AUTHORS |
---|---|---|
1 | How to Play | Author1 |
2 | How to Sing | Author2 |
3 | How to Dance | Author3 |
4 | How to be Flexible | Author4 |
A practical use case would be finding all books written by authors whose hobby contains the word 'dance', which we will explore in this article.
Deep Filtering Using the REST API
The following API request returns all books
written by authors
whose hobby contains the word 'dance':
Sample Request:
1GET /api/books?filters[authors][hobby][$contains]=dance
Sample Response:
1{
2 "data": [
3 {
4 "id": 10,
5 "documentId": "kte9yxf6oami3680br3gapfl",
6 "title": "How to be Flexible",
7 "createdAt": "2025-01-16T05:49:53.037Z",
8 "updatedAt": "2025-01-16T06:06:43.838Z",
9 "publishedAt": "2025-01-16T06:06:43.851Z"
10 },
11 {
12 "id": 6,
13 "documentId": "mfnvnveobtf4fzporv9k2z3x",
14 "title": "How to Dance",
15 "createdAt": "2025-01-16T05:49:27.517Z",
16 "updatedAt": "2025-01-16T05:49:27.517Z",
17 "publishedAt": "2025-01-16T05:49:27.522Z"
18 }
19 ],
20 "meta": {
21 "pagination": {
22 "page": 1,
23 "pageSize": 25,
24 "pageCount": 1,
25 "total": 2
26 }
27 }
28}
Queries accept a filters
parameter with the following syntax:
1GET /api/:pluralApiId?filters[field][operator]=value
Strapi supports a wide range of query operators. A comprehensive list of query operators can be found in the Strapi documentation.
Let's examine the example request:
1GET /api/books?filters[authors][hobby][$contains]=dance
- The
/api/books
endpoint without query parameters returns all books, subject to the default limit - The
?filters
parameter enables result filtering [authors]
represents a relation field in theBook
collection type[hobby]
represents a field in theAuthor
collection type[$contains]
specifies the operator that Strapi supports=dance
specifies the search value
This query can be constructed programmatically in the frontend:
1const qs = require('qs');
2const query = qs.stringify({
3 filters: {
4 authors: {
5 hobby: {
6 $contains: dance,
7 },
8 },
9 },
10}, {
11 encodeValuesOnly: true,
12});
13
14await request(`/api/restaurants?${query}`);
15// GET /api/restaurants?filters[authors][hobby][$contains]=dance
Deep Filtering Using the Query Engine API
The Query Engine API allows users to filter results using its findMany() method.
The filters
parameter is used to filter results. It supports both logical operators and attribute operators. Each operator must be prefixed with $
.
The query equivalent for GET /api/books?filters[authors][hobby][$contains]=dance
is as follows:
1module.exports = {
2 async index(ctx, next) {
3 const entries = await strapi.db.query('api::book.book').findMany({
4 where: {
5 authors: {
6 hobby: {
7 $contains: 'Dance'
8 },
9 }
10 },
11 });
12 ctx.body = entries;
13 }
14};
To try the above query, you may want to create a controller, as demonstrated in the video below.
- Execute the following command in the terminal to create a controller named
dancebooks
:
npx strapi generate
- Select the following options:
1? Strapi Generators controller - Generate a controller for an API
2? Controller name dancebooks
3? Where do you want to add this controller? Add controller to new API
4✔ ++ /api/dancebooks/controllers/dancebooks.js
- Create a directory for routes:
mkdir src/api/dancebooks/routes
- In the
routes
directory, create a file calleddancebooks.js
and add the following route:
1module.exports = {
2 routes: [
3 {
4 method: 'GET',
5 path: '/dance-books',
6 handler: 'dancebooks.index'
7 }
8 ]
9}
- Replace the content of
src/api/dancebooks/controllers/dancebooks.js
with the following lines of code:
1'use strict';
2
3module.exports = {
4 async index(ctx) {
5 try {
6 const entries = await strapi.db.query('api::book.book').findMany({
7 where: {
8 authors: {
9 hobby: {
10 $contains: 'Dance',
11 },
12 },
13 },
14 });
15
16 ctx.body = entries; // Send the query results
17 } catch (err) {
18 ctx.throw(500, 'Failed to fetch dance books');
19 }
20 },
21};
Once the server restarts and a GET request is made to /api/dance-books
, the expected response will be received. Permission must be granted to the public to access the route.
1[
2 {
3 "id": 5,
4 "documentId": "mfnvnveobtf4fzporv9k2z3x",
5 "title": "How to Dance",
6 "createdAt": "2025-01-16T05:49:27.517Z",
7 "updatedAt": "2025-01-16T05:49:27.517Z",
8 "publishedAt": null,
9 "locale": null
10 },
11 {
12 "id": 6,
13 "documentId": "mfnvnveobtf4fzporv9k2z3x",
14 "title": "How to Dance",
15 "createdAt": "2025-01-16T05:49:27.517Z",
16 "updatedAt": "2025-01-16T05:49:27.517Z",
17 "publishedAt": "2025-01-16T05:49:27.522Z",
18 "locale": null
19 },
20 {
21 "id": 7,
22 "documentId": "kte9yxf6oami3680br3gapfl",
23 "title": "How to be Flexible",
24 "createdAt": "2025-01-16T05:49:53.037Z",
25 "updatedAt": "2025-01-16T06:06:43.838Z",
26 "publishedAt": null,
27 "locale": null
28 },
29 {
30 "id": 10,
31 "documentId": "kte9yxf6oami3680br3gapfl",
32 "title": "How to be Flexible",
33 "createdAt": "2025-01-16T05:49:53.037Z",
34 "updatedAt": "2025-01-16T06:06:43.838Z",
35 "publishedAt": "2025-01-16T06:06:43.851Z",
36 "locale": null
37 }
38]
Deep Filtering Using the Entity Service API
The same result can be achieved using the Entity Service API. Replace the code in src/api/dancebooks/controllers/dancebooks.js
with the following:
1'use strict';
2
3 module.exports = {
4 async index(ctx, next) {
5 const entries = await strapi.entityService.findMany('api::book.book', {
6 filters: {
7 authors: {
8 hobby: {
9 $contains: 'Dance'
10 },
11 }
12 },
13 });
14 ctx.body = entries;
15 }
16};
Restart the server and send a GET request to /api/dance-books
. The expected response is as follows:
1[
2 {
3 "id": 5,
4 "documentId": "mfnvnveobtf4fzporv9k2z3x",
5 "title": "How to Dance",
6 "createdAt": "2025-01-16T05:49:27.517Z",
7 "updatedAt": "2025-01-16T05:49:27.517Z",
8 "publishedAt": null,
9 "locale": null
10 },
11 {
12 "id": 7,
13 "documentId": "kte9yxf6oami3680br3gapfl",
14 "title": "How to be Flexible",
15 "createdAt": "2025-01-16T05:49:53.037Z",
16 "updatedAt": "2025-01-16T06:06:43.838Z",
17 "publishedAt": null,
18 "locale": null
19 }
20]
The Entity Service API is built upon the Query Engine API. The Entity Service layer handles Strapi's complex data structures, such as components and dynamic zones, using the Query Engine API to execute database queries.
Deep Filtering Using the GraphQL API
- Install the GraphQL plugin in your Strapi project:
npm install @strapi/plugin-graphql
- Navigate to the GraphQL Playground at localhost:1337/graphql.
The following GraphQL query will return all books where the author's hobby contains the word “dance”:
1query {
2 books (filters: { authors:{hobby : { contains: "dance" }}} ){
3 authors {
4 hobby
5 }
6 title
7 }
8}
The GraphQL Playground should resemble the image below:
Deep Filtering Using the Document Service API
The Document Service API is built on top of the Query Engine API and is used to perform CRUD operations on documents: create, retrieve, update, and delete.
Replace the contents of src/api/dancebooks/controllers/dancebooks.js
with the following:
1'use strict';
2
3module.exports = {
4 async index(ctx) {
5 try {
6 const entries = await strapi.documents('api::book.book').findMany({
7 filters: {
8 authors: {
9 hobby: {
10 $contains: 'Dance',
11 },
12 },
13 },
14 });
15
16 ctx.body = entries;
17 } catch (error) {
18 ctx.throw(500, 'Failed to fetch dance books');
19 }
20 },
21};
Restart the server and send a GET request to /api/dance-books
. The expected response is as follows:
1[
2 {
3 "id": 5,
4 "documentId": "mfnvnveobtf4fzporv9k2z3x",
5 "title": "How to Dance",
6 "createdAt": "2025-01-16T05:49:27.517Z",
7 "updatedAt": "2025-01-16T05:49:27.517Z",
8 "publishedAt": null,
9 "locale": null
10 },
11 {
12 "id": 7,
13 "documentId": "kte9yxf6oami3680br3gapfl",
14 "title": "How to be Flexible",
15 "createdAt": "2025-01-16T05:49:53.037Z",
16 "updatedAt": "2025-01-16T06:06:43.838Z",
17 "publishedAt": null,
18 "locale": null
19 }
20]
The Document Service API is meant to replace the Entity Service API.
GitHub Source Code
The source code for this article can be found in this GitHub Repo.
Conclusion
This article demonstrated how to filter deeply nested data using relationship fields in Strapi. You can learn more about these exciting features of Strapi in this excellent blog: What is New for Developers in Strapi 5: Top 10 Changes. Let me know if you have any suggestions and what you plan to build with this knowledge.
When I’m not working, I love to write, learn something new & read non-fiction.
Mark Munyaka is a freelance web developer and writer who loves problem-solving and testing out web technologies. You can follow him on Twitter @McMunyaka