Content Delivery API > Deep Filtering

Deep Filtering

Modular Content and Structured Text fields can embed blocks, which are dynamic, flexible, and repeatable structures. When referring to the Content Delivery API, deep filtering allows you to filter records based on the content within their embedded blocks.

Activate deep filtering

Deep filtering is a feature that needs to be explicitly enabled on a per-field basis. This is to avoid complicating — and therefore slowing down — your GraphQL schema by generating a large number of types that will never actually be used.

To activate the feature, go to a field's editing modal, and enable the deep filtering option:

Once enabled, new filters become available in the GraphQL Content Delivery API for the specified field.

To be precise, there is a second necessary condition to see the newly activated GraphQL filters: the field must accept at least one block type. If the field does not allow embedding any block types within it, then deep filtering is not applicable — there's nothing to filter! — and therefore the GraphQL filters relative to deep filtering will not be present.

Pro tip: Explore use cases for deep filtering

Check out this blog entry for an in-depth look at use cases where deep filtering can simplify accessing specific data without multiple API calls, enhancing performance and efficiency.

Modular Content

In the following examples, let's consider a blog_post model with a content Modular Content field that accepts two types of blocks: hero and product.

Filter records containing at least one block matching the specified conditions

To retrieve blog_post records that contain a product block with a name equal to "T-Shirt", and a price greater than 30, you can use the following GraphQL query:

query {
allBlogPosts(
filter: {
content: {
any: { product: { name: { eq: "T-Shirt" }, price: { gt: 30 } } }
}
}
) {
# ...
}
}

In other words, within a field with deep filtering enabled, you can specify an any key where, for each block type that the field accepts, you can define one or more filtering conditions. The word "any" can be read as: "find records where any product block respects these conditions".

The filtering conditions are the same ones discussed in the previous section on Filtering records, with the only difference being that since blocks don't have meta fields like ie. _firstPublishedAt they cannot be used in this context. The only meta key available for blocks is id.

Specifying conditions for multiple types of block

If you specify conditions for more than one type of block, then all conditions must be respected.

query {
allBlogPosts(
filter: {
content: {
any: {
product: { name: { eq: "T-Shirt" } }
hero: { title: { matches: { pattern: "offer" } } }
}
}
}
) {
# ...
}
}

The example above will search for all blog posts that have both a product block called "T-Shirt", and a hero block with a title that contains the term "offer".

Putting conditions in OR

If you want to apply logical OR conditions between various conditions, you can always use the OR filter.

The previous query can be modified to return blog posts that have either a "T-Shirt" product block or an "offer" hero block, like this:

query {
allBlogPosts(
filter: {
OR: [
{
content: {
any: { product: { name: { eq: "T-Shirt" } } }
}
},
{
content: {
any: { hero: { title: { matches: { pattern: "offer" } } } }
}
}
]
}
) {
# ...
}
}

Filter records containing at least one block, of any kind

If you are only interested into filtering records that, in a particular field, contain at least one block, regardless of its type, you can use the exists filter:

query {
allBlogPosts(
filter: {
content: { exists: true }
}
) {
# ...
}
}

Inverting the condition into exists: false will find all blog posts that don't have any block in the field.

Filter records containing at least one block of specified type

If you want to be more specific and filter records that contain at least one block of one or more specific types, then you can use the "containsAny": true filter. You can also search for records that do not contain any blocks of a specific type with"containsAny": false.

The following query returns all blog posts that contain at least one block of type product, but do not contain any blocks of type hero:

query {
allBlogPosts(
filter: {
content: {
containsAny: { product: true, hero: false }
}
}
) {
# ...
}
}

Structured Text

When deep filtering is not enabled on a Structured Text field, its GraphQL filters allow to filter by its textual content only, like this:

query {
allProducts(
filter: {
structuredTextField: {
matches: { pattern: "bi(cycl|k)e", caseSensitive: false }
}
}
) {
# ...
}
}

However, when you activate deep filtering, the filter format will change, and the value argument will group together all filters related to the textual content.

To put it differently, when deep filtering is turned on, the previous query needs to be rewritten as:

query {
allProducts(
filter: {
structuredTextField: {
value: { # this argument has been introduced
matches: { pattern: "bi(cycl|k)e", caseSensitive: false }
}
}
}
) {
# ...
}
}
This means a breaking change in GraphQL schema!

We just saw that enabling deep filtering on a Structured Text field will cause a change to the associated GraphQL filter type. This means that existing GraphQL queries may need to be rewritten in order to avoid errors.

This is another reason why deep filtering is activable or not on a per-field basis: so that you are in control of when (and how) to introduce this change.

To avoid unpleasant surprises in production, it is a good idea to test the switch to deep filtering for Structured Text fields in a a sandbox environment first, and see if it breaks any of your existing GraphQL queries.

All the query possibilities in deep filtering mentioned above for the Modular Content fields also apply to the Structured Text fields.

The only distinction is that all the filter arguments related to blocks are nested inside the blocks argument:

query {
allProducts(
filter: {
structuredTextField: {
blocks: {
any: { cta: { title: { eq: "Subscribe!" } }
},
value: {
matches: { pattern: "bi(cycl|k)e", caseSensitive: false }
}
}
}
) {
# ...
}
}
}

Known Issues

Deep Filtering Technical Limitations

Depth Limit:

Deep filtering is currently limited to only one level of depth. That is, you cannot filter records based on the content of blocks deeply nested inside other blocks. For example, if you have:

  • A modular content field

    • A parent block with a "parent title" field and another modular content field called "child blocks"

      • Inside a child block, you have another field called "child title"

You can filter by the "parent title" but you cannot filter by the "child title".

Content Delivery API only:

Deep filtering is currently limited to the Content Delivery API (CDA / GraphQL). You cannot deep filter records in the Content Management API (CMA / REST).