The Drupal core JSON:API module implements the JSON:API spec for Drupal entities. It provides a zero-configuration required, opinionated, way to allow RESTful CRUD for a Drupal site’s content.
It is closely coupled to Drupal’s Entity and Field APIs, response caching, and authentication and authorization systems. Because it follows the shared JSON:API conventions it can help increase productivity and allow you to take advantage of non-Drupal specific tooling.
Collections are listings of resources. When you make an unfiltered request to a collection endpoint like /jsonapi/node/article, you’ll just get every article that you’re allowed to see. Without filters, you can’t get only the articles that you want.
The simplest, most common filter is a key-value filter:
A group is a set of conditions joined by a conjunction, either AND or OR. Let’s say we want to find all users with a last name that starts with “J” and have either the first name “Janis” or “Joan”. To do that, we add a group:
/data? filter[gID-1][group][conjunction]=OR
Then, we need assign filters to that new group. To do that, we add a memberOf key. Every condition and group can have a memberOf key.
Note: Groups can have a memberOf key just like conditions, which means you can have groups of groups. Every filter without a memberOf key is assumed to be part of a root group with a conjunction of AND.
Does that look familiar? It should, we saw it above as a tree:
A A = root & g1/ \/ \ B = f3B & C C = g1/ \/ \ D = f1 D | E E = f2
Paths
Paths provide a way to filter based on relationship values. Up to this point, we’ve just been filtering by the hypothetical first_name and last_name. Suppose we want to filter by the name of a user’s career, where career types are stored as a separate resource. We could add a filter like this:
Paths use a “dot notation” to traverse relationships. If a resource has a relationship, you can add a filter against it by concatenating the relationship field name and the relationship’s field name with a . (dot). You can even filter by relationships of relationships (and so on) just by adding more field names and dots.
You can filter on a specific index of a relationship by putting a non-negative integer in the path. So the path some_relationship.1.some_attribute would only filter by the 2nd related resource.
Tip: You can filter by sub-properties of a field. For example, a path like field_phone.country_code will work even though field_phone isn’t a relationship.
When filtering against configuration properties, you can use an asterisk (*) to stand-in for any portion of a path.
When the operator is = and you don’t need to filter by the same field twice, the path can be the identifier:
/data? filter[FIRST_NAME][value]=Janis
Reduce the simplest equality checks down to a key-value form:
/data? filter[FIRST_NAME]=Janis
Filters and Access Control
First, a warning: don’t make the mistake of confusing filters for access control. Just because you’ve written a filter to remove something that a user shouldn’t be able to see, doesn’t mean it’s not accessible. Always perform access checks on the backend.
With that big caveat, let’s talk about using filters to complement access control. To improve performance, you should filter out what your users will not be able to see. The most frequent support request in the JSON:API issue queues can be solved by this one simple trick! If you know your users cannot see unpublished content, add the following filter:
/data? filter[status][value]=1
Using this method, you’ll lower the number of unnecessary requests that you need to make. That’s because JSON:API doesn’t return data for resources to which a user doesn’t have access. You can see which resources may have been affected by inspecting the meta.errors section of JSON:API document. So, do your best to filter out inaccessible resources ahead of time.
Examples
Exact Match on Column
/data? filter[PROVIDER_TYPE_DESC]=PRACTITIONER - GENERAL PRACTICE
The keyword search will look for matching words in every column. This example will check for “Alex” in the Order and Referring dataset. Notice that it finds matches on both the first and last name fields.
/data? keyword=Alex
Multiple Conditions at Once
This search returns results from the Medicare Fee-For-Service Public Provider Enrollment dataset where the provider specialty is “PRACTITIONER - OPTOMETRY” and the location is Virginia.
This search returns results from the Opioid Treatment Program Providers dataset where the provider is located from MD, MI, or VA with the results sorted by NPI.
When utilizing square brackets for multiple values filters, do not just use empty square brackets for a new value. While these work when typed into the URL, Guzzle and other HTTP clients will only create one value, as the array key will be seen to be the same and override the previous value. It is better to use an index to create unique array elements.
Note the two square brackets added behind the value to make it into an array:
A common strategy is to filter content by a entity reference.
SHORT filter[uid.id][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CDNORMAL filter[fID-1][condition][path]=uid.id filter[fID-1][condition][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CD
To fully comply with the JSON API specification, while Drupal internally uses the uuid property, JSON:API uses id instead.
Since Drupal 9.3 it is possible to filter on target_id also instead of only filtering by uuid property.
It’s possible to filter on fields from referenced entities like the user, taxonomy fields or any entity reference field. You can do this easily but just using the the following notation. reference_field.nested_field. In this example the reference field is uid for the user and name which is a field of the user entity.
You don’t really have to add the and-group but I find that a bit easier usually.
Grouping Grouped Filters
Like mentioned in the grouping section, you can put groups into other groups. WHERE (user.name = admin) AND (node.sticky = 1 OR node.promoted = 1)
To do this we put sticky and promoted into a group with conjunction OR. Create a group with conjunction AND and put the admin filter, and the promoted/sticky OR group into that.
# Create an AND and an OR GROUPfilter[and-group][group][conjunction]=ANDfilter[or-group][group][conjunction]=OR# Put the OR group into the AND GROUPfilter[or-group][group][memberOf]=and-group# Create the admin filter and put it in the AND GROUPfilter[admin-filter][condition][path]=uid.namefilter[admin-filter][condition][value]=adminfilter[admin-filter][condition][memberOf]=and-group# Create the sticky filter and put it in the OR GROUPfilter[sticky-filter][condition][path]=stickyfilter[sticky-filter][condition][value]=1filter[sticky-filter][condition][memberOf]=or-group# Create the promoted filter and put it in the OR GROUPfilter[promote-filter][condition][path]=promotefilter[promote-filter][condition][value]=1filter[promote-filter][condition][memberOf]=or-group
Filter by non-standard complex fields (e.g. addressfield)
FILTER BY LOCALITYfilter[field_address][condition][path]=field_address.localityfilter[field_address][condition][value]=MordorFILTER BY ADDRESS LINEfilter[address][condition][path]=field_address.address_line1filter[address][condition][value]=Rings Street
Filtering on Taxonomy term values (e.g. tags)
For filtering you’ll need to use the machine name of the vocabulary and the field which is present on your node.
This example is for a Checkboxes/Radio buttons field with no value selected. Consider you have a field that is a checkbox. You would like to get all nodes that do not have that value checked. When checked, the JSON API returns an array:
"my_field":["checked"]
When unchecked, the JSON API returns an empty array:
"my_field": []
If you would like to get all fields that are unchecked, you must use the IS NULL on the array as follows (without a value):
# JSON:API Syntax {#sec-jsonapi}```{r}#| label: setup-common-01#| include: falsesource("includes/_common.R")```## Links * [Drupal JSON:API](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/filtering)## JSON:APIThe Drupal core [JSON:API module](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module) implements the JSON:API spec for Drupal entities. It provides a zero-configuration required, opinionated, way to allow RESTful CRUD for a Drupal site's content.It is closely coupled to Drupal's Entity and Field APIs, response caching, and authentication and authorization systems. Because it follows the shared JSON:API conventions it can help increase productivity and allow you to take advantage of non-Drupal specific tooling.[Refer to the Drupal documentation](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/filtering) for more information on filtering API requests.## Filtering[Collections](https://www.drupal.org/docs/8/modules/json-api/collections-filtering-and-sorting) are listings of resources. When you make an unfiltered request to a collection endpoint like `/jsonapi/node/article`, you'll just get every article that you're allowed to see. Without filters, you can't get only the articles that you want.The simplest, most common filter is a **key-value filter**:```r/data? filter[field_1]=value_1& filter[field_2]=value_2```This matches all resources with `field_1` equal to `"value_1"` and `field_2` equal to `"value_2"`.### ConditionsThe fundamental building blocks of JSON:API filters are **Conditions** and **Groups**. * __Conditions__ assert that something is `TRUE` * __Groups__ compose sets of conditionsThose sets can be nested to make super fine queries. You can think of a nested set as a tree:_Conventional representation:_```{r}is_A <- \(x) any(x =="A")is_B <- \(x) any(x =="B")is_C <- \(x) any(x =="C")is_D <- \(x) any(x =="D")is_E <- \(x) any(x =="E")x <-c("A", "B", "C", "D", "E")is_A(x[is_B(x) &is_C(x[is_D(x) |is_E(x)])])x <-c("A", "B", "C", "F", "G")is_A(x[is_B(x) &is_C(x[is_D(x) |is_E(x)])])```_Tree representation:_```r A/ \ B & C/ \ D | E```In both representations: * `D` & `E` are members of `C` in an `OR` group * `B` & `C` are members of `A` in an `AND` groupA **condition** has 3 primary parts: 1. `[path]`: Dataset **Field** 1. `[operator]`: Logical comparison 1. `[value]`: Dataset **Row**In **JSON:API** syntax, these are formatted as a `key=value` pair to work inside a URL query string:```r/data? filter[fID-1][condition][path]=FIRST_NAME& filter[fID-1][condition][operator]=%3D& filter[fID-1][condition][value]=Janis```> __Note__: `%3D` is the URL-encoded `=` symbol. All operators must be URL-encoded.Notice the `ID` inside the first set of square brackets. _Every condition or group should have a unique identifier._Let's add another filter so we only get Janises with a last name that starts with `"J"`:```r/data? filter[fID-1][condition][path]=FIRST_NAME& filter[fID-1][condition][operator]=%3D& filter[fID-1][condition][value]=Janis& filter[fID-2][condition][path]=LAST_NAME& filter[fID-2][condition][operator]=STARTS_WITH& filter[fID-2][condition][value]=J```### GroupsA **group** is a set of conditions joined by a **conjunction**, either `AND` or `OR`. Let's say we want to find all users with a last name that starts with "J" and have either the first name "Janis" or "Joan". To do that, we add a group:```r/data? filter[gID-1][group][conjunction]=OR```Then, we need assign filters to that new group. To do that, we add a `memberOf` key. Every condition and group can have a `memberOf` key. ```r/data? filter[gID-1][group][conjunction]=OR& filter[fID-1][condition][path]=first_name& filter[fID-1][condition][operator]=%3D& filter[fID-1][condition][value]=Janis& filter[fID-1][condition][memberOf]=gID-1& filter[fID-2][condition][path]=first_name& filter[fID-2][condition][operator]=%3D& filter[fID-2][condition][value]=Joan& filter[fID-2][condition][memberOf]=gID-1& filter[fID-3][condition][path]=last_name& filter[fID-3][condition][operator]=STARTS_WITH& filter[fID-3][condition][value]=J```> __Note__: Groups can have a `memberOf` key just like conditions, which means you can have groups of groups. Every filter without a `memberOf` key is assumed to be part of a `root` group with a conjunction of `AND`.Does that look familiar? It should, we saw it above as a tree:```r A A = root & g1/ \/ \ B = f3B & C C = g1/ \/ \ D = f1 D | E E = f2```### PathsPaths provide a way to filter based on relationship values. Up to this point, we've just been filtering by the hypothetical `first_name` and `last_name`. Suppose we want to filter by the name of a user's career, where career types are stored as a separate resource. We could add a filter like this:```r/data? filter[career][condition][path]=field_career.name& filter[career][condition][operator]=%3D& filter[career][condition][value]=DOCTOR```Paths use a "dot notation" to traverse relationships. If a resource has a relationship, you can add a filter against it by concatenating the relationship field name and the relationship's field name with a `.` (dot). You can even filter by relationships of relationships (and so on) just by adding more field names and dots.You can filter on a specific index of a relationship by putting a non-negative integer in the path. So the path `some_relationship.1.some_attribute` would only filter by the 2nd related resource.> Tip: You can filter by sub-properties of a field. For example, a path like `field_phone.country_code` will work even though `field_phone` isn't a relationship.When filtering against configuration properties, you can use an asterisk (`*`) to stand-in for any portion of a path. For example, ```r/api/field_config/field_config? filter[dependencies.config.*]=comment.type.comment```would match all field configs in which `[attributes][dependencies][config]` (an indexed array) contains the value `"comment.type.comment"`.### ShortcutsWhen the operator is `=`, you don't have to include it:```r/data? filter[fID-1][condition][path]=FIRST_NAME& filter[fID-1][condition][operator]=%3D& filter[fID-1][condition][value]=Janis```_becomes_```r/data? filter[fID-1][condition][path]=FIRST_NAME& filter[fID-1][condition][value]=Janis```When the operator is `=` and you don't need to filter by the same field twice, the path can be the identifier:```r/data? filter[FIRST_NAME][value]=Janis```Reduce the simplest equality checks down to a _key-value_ form:```r/data? filter[FIRST_NAME]=Janis```### Filters and Access ControlFirst, a warning: don't make the mistake of confusing filters for access control. Just because you've written a filter to remove something that a user shouldn't be able to see, doesn't mean it's not accessible. **Always perform access checks on the backend.**With that big caveat, let's talk about using filters to complement access control. To improve performance, you should filter out what your users will not be able to see. The most frequent support request in the JSON:API issue queues can be solved by this one simple trick! If you know your users cannot see unpublished content, add the following filter:```r/data? filter[status][value]=1```Using this method, you'll lower the number of unnecessary requests that you need to make. **That's because JSON:API doesn't return data for resources to which a user doesn't have access.** You can see which resources may have been affected by inspecting the meta.errors section of JSON:API document. So, do your best to filter out inaccessible resources ahead of time.## Examples### Exact Match on Column```r/data? filter[PROVIDER_TYPE_DESC]=PRACTITIONER - GENERAL PRACTICE```### CONTAINS Search on One Column```r/data? filter[fID-1][condition][path]=PROVIDER_TYPE_DESC& filter[fID-1][condition][operator]=CONTAINS& filter[fID-1][condition][value]=SUPPLIER```### CONTAINS & EQUALS Combination Search on Two Columns```r/data? filter[fID-1][condition][path]=PROVIDER_TYPE_DESC& filter[fID-1][condition][operator]=CONTAINS& filter[fID-1][condition][value]=PRACTITIONER& filter[fID-2][condition][path]=STATE_CD& filter[fID-2][condition][operator]=%3D& filter[fID-2][condition][value]=MD```### EQUALS SimplifiedThis example is an equals filter searching the Accountable Care Organizations 2021 dataset for a single ID.```r/data? filter[fID-1][condition][path]=aco_id& filter[fID-1][condition][operator]=%3D& filter[fID-1][condition][value]=A4807```An equals filter can be simplified like this.```r/data? filter[aco_id]=A4807```### KeywordThe keyword search will look for matching words in every column. This example will check for "Alex" in the Order and Referring dataset. Notice that it finds matches on both the first and last name fields.```r/data? keyword=Alex```### Multiple Conditions at OnceThis search returns results from the Medicare Fee-For-Service Public Provider Enrollment dataset where the provider specialty is "PRACTITIONER - OPTOMETRY" and the location is Virginia.```r/data?& filter[ROOT][group][conjunction]=AND& filter[GID-1][group][conjunction]=AND& filter[GID-1][group][memberOf]=ROOT& filter[FID-1][condition][path]=PROVIDER_TYPE_DESC& filter[FID-1][condition][operator]=%3D& filter[FID-1][condition][value]=PRACTITIONER - OPTOMETRY& filter[FID-1][condition][memberOf]=GID-1& filter[FID-2][condition][path]=STATE_CD& filter[FID-2][condition][operator]=%3D& filter[FID-2][condition][value]=VA& filter[FID-2][condition][memberOf]=GID-1```### INThis search returns results from the Opioid Treatment Program Providers dataset where the provider is located from MD, MI, or VA with the results sorted by NPI.```r/data? filter[condition][path]=STATE& filter[condition][operator]=IN& filter[condition][value][]=MI& filter[condition][value][]=VA& filter[condition][value][]=MD& sort=NPI```**Note About Empty Brackets:**When utilizing square brackets for multiple values filters, *do not just use empty square brackets for a new value.*While these work when typed into the URL, Guzzle and other HTTP clients will only create one value, as the array key will be seen to be the same and override the previous value. **It is better to use an index to create unique array elements.**Note the two square brackets added behind the value to make it into an array:```r/data? filter[fID-1][condition][path]=STATE& filter[fID-1][condition][operator]=IN& filter[fID-1][condition][value][1]=MI& filter[fID-1][condition][value][2]=VA& filter[fID-1][condition][value][3]=MA```### Sort ResultsUse the `sort` query parameter to specify which column the results should be sorted by:```rLowest first:/data? sort=NPIHighest first:/data? sort=-NPI```### Subset ColumnsAdd a comma-separated string of column names to the `column` query parameter to limit the columns returned:```r/data? column=NPI,FIRST_NAME,LAST_NAME```### Only Published NodesA very common scenario is to only load the nodes that are published. This is a very easy filter to add.```rSHORT filter[status][value]=1NORMAL filter[fID-1][condition][path]=status filter[fID-1][condition][value]=1```### Nodes by Value of Entity ReferenceA common strategy is to filter content by a entity reference.```rSHORT filter[uid.id][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CDNORMAL filter[fID-1][condition][path]=uid.id filter[fID-1][condition][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CD```To fully comply with the JSON API specification, while Drupal internally uses the `uuid` property, __JSON:API__ uses `id` instead.Since Drupal 9.3 it is possible to filter on `target_id` also instead of only filtering by `uuid` property.```rSHORT filter[field_tags.meta.drupal_internal__target_id]=1NORMAL filter[fID-1][condition][path]=field_tags.meta.drupal_internal__target_id filter[fID-1][condition][value]=1```### Nested FiltersIt's possible to filter on fields from referenced entities like the user, taxonomy fields or any entity reference field. You can do this easily but just using the the following notation. reference_field.nested_field. In this example the reference field is uid for the user and name which is a field of the user entity.```rSHORT filter[uid.name][value]=adminNORMAL filter[fID-1][condition][path]=uid.name filter[fID-1][condition][value]=admin```### Filtering with ArraysYou can give multiple values to a filter for it to search in. Next to the field and value keys you can add an operator to your condition. Usually it's `"="` but you can also use `"IN"`, `"NOT IN"`, `">"`, `"<"`, `"<>"`, `BETWEEN`".For this example we're going to use the `"IN"` operator. Note that I added two square brackets behind the value to make it into an array.```rNORMAL filter[fID-1][condition][path]=uid.name filter[fID-1][condition][operator]=IN filter[fID-1][condition][value][1]=admin filter[fID-1][condition][value][2]=john```### Grouping FiltersNow let's combine some of the examples above and create the following scenario.WHERE user.name = admin AND node.status = 1:```rfilter[and-group][group][conjunction]=ANDfilter[name-filter][condition][path]=uid.namefilter[name-filter][condition][value]=adminfilter[name-filter][condition][memberOf]=and-groupfilter[status-filter][condition][path]=statusfilter[status-filter][condition][value]=1filter[status-filter][condition][memberOf]=and-group```You don't really have to add the and-group but I find that a bit easier usually.### Grouping Grouped FiltersLike mentioned in the grouping section, you can put groups into other groups.WHERE (user.name = admin) AND (node.sticky = 1 OR node.promoted = 1)To do this we put sticky and promoted into a group with conjunction OR. Create a group with conjunction AND and putthe admin filter, and the promoted/sticky OR group into that.```r# Create an AND and an OR GROUPfilter[and-group][group][conjunction]=ANDfilter[or-group][group][conjunction]=OR# Put the OR group into the AND GROUPfilter[or-group][group][memberOf]=and-group# Create the admin filter and put it in the AND GROUPfilter[admin-filter][condition][path]=uid.namefilter[admin-filter][condition][value]=adminfilter[admin-filter][condition][memberOf]=and-group# Create the sticky filter and put it in the OR GROUPfilter[sticky-filter][condition][path]=stickyfilter[sticky-filter][condition][value]=1filter[sticky-filter][condition][memberOf]=or-group# Create the promoted filter and put it in the OR GROUPfilter[promote-filter][condition][path]=promotefilter[promote-filter][condition][value]=1filter[promote-filter][condition][memberOf]=or-group```### Filter for nodes where 'title' CONTAINS "Foo"```rSHORTfilter[title][operator]=CONTAINS&filter[title][value]=FooNORMALfilter[title-filter][condition][path]=titlefilter[title-filter][condition][operator]=CONTAINSfilter[title-filter][condition][value]=Foo```### Filter by non-standard complex fields (e.g. addressfield)```rFILTER BY LOCALITYfilter[field_address][condition][path]=field_address.localityfilter[field_address][condition][value]=MordorFILTER BY ADDRESS LINEfilter[address][condition][path]=field_address.address_line1filter[address][condition][value]=Rings Street```### Filtering on Taxonomy term values (e.g. tags)For filtering you'll need to use the machine name of the vocabulary and the field which is present on your node.```rfilter[taxonomy_term--tags][condition][path]=field_tags.namefilter[taxonomy_term--tags][condition][operator]=INfilter[taxonomy_term--tags][condition][value][]=tagname```### Filtering on Date (Date only, no time)Dates are filterable. Pass a time string that adheres to the ISO-8601 format.This example is for a Date field that is set to be date only (no time).```rfilter[datefilter][condition][path]=field_test_datefilter[datefilter][condition][operator]=%3Dfilter[datefilter][condition][value]=2019-06-27```This example is for a Date field that supports date and time.```rfilter[datefilter][condition][path]=field_test_datefilter[datefilter][condition][operator]=%3Dfilter[datefilter][condition][value]=2019-06-27T16%3A00%3A00```Note that timestamp fields (like created or changed) currently must use a timestamp for filtering:```rfilter[recent][condition][path]=createdfilter[recent][condition][operator]=%3Dfilter[recent][condition][value]=1591627496```### Filtering on empty array fieldsThis example is for a Checkboxes/Radio buttons field with no value selected. Consider you have a field that is a checkbox. You would like to get all nodes that do not have that value checked. When checked, the JSON API returns an array:```r"my_field":["checked"]```When unchecked, the JSON API returns an empty array:```r"my_field": [] ```If you would like to get all fields that are unchecked, you must use the IS NULL on the array as follows (without a value):```rfilter[my-filter][condition][path]=my_fieldfilter[my-filter][condition][operator]=IS NULL```