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.

    The field must accept at least one block type

    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.

    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 limitations

    As of today, deep filtering is limited to one level of depth. That is, you cannot filter records based on the content of nested blocks deeply embedded inside a field.