Find Entries Using a Matrix Block Type in Craft CMS

Find Entries Using a Matrix Block Type in Craft CMS

Alex Rollin
Alex Rollin
January 27, 2026
Last updated : February 15, 2026
January 27, 2026

Deleting a Matrix block type feels risky when you don't know what might break. You're about to rename your `callToAction` block, or maybe remove `legacyPromo` entirely, but Craft's control panel won't tell you which entries actually use these blocks.

This gap in Craft's built-in search functionality catches many developers off guard. The search indexes Matrix block content, not block type handles. That means searching for "callToAction" won't find entries that contain that block type.

We've learned that this problem comes up repeatedly before any significant Matrix field refactoring. This guide walks you through the practical approaches for identifying block type usage across your content, covering both Craft 3/4 and the architectural changes in Craft 5.

Prerequisites

Before implementing these approaches, you'll need:

  • Access to your Craft CMS templates (Twig files)
  • Basic familiarity with Craft element queries
  • Knowledge of your Matrix field handle and the block type handle you're searching for
  • Admin or developer access to your Craft installation

For the database approach, you'll also need direct database access. Understanding Craft CMS element queries Matrix fundamentals will help you adapt these techniques to your specific setup.

Understanding the Problem

Craft CMS entry queries can filter Matrix fields in a few ways:

  • :empty: finds entries where the Matrix field has no content
  • :notempty: finds entries where the Matrix field contains blocks
  • Specific block IDs like 100 or [100, 200] filter by known block instances

What's missing is the ability to say "find all entries that contain at least one block of type X." The block type handle isn't a searchable parameter on entry queries.

The approach that works: query Matrix blocks first, then work backwards to find their owners.

Step-by-Step Implementation for Craft 3 and 4

Step 1: Set Up a Diagnostic Template

Create a temporary template file, something like templates/_dev/block-finder.twig. This keeps your diagnostic code separate from production templates.

Step 2: Query Matrix Blocks by Type

Use craft.matrixBlocks() with the type parameter to find all blocks matching your target type:

{% set targetBlocks = craft.matrixBlocks()
    .fieldId(123)
    .type('callToAction')
    .all() %}

Replace 123 with your Matrix field's ID, and 'callToAction' with your block type handle. This craft.matrixBlocks query approach is the foundation for tracking Craft CMS Matrix block type usage across your site.

Step 3: Extract Owner Information

Each Matrix block knows its owner. Loop through the blocks and collect the parent entries:

{% set ownerIds = [] %}
{% for block in targetBlocks %}
    {% set ownerIds = ownerIds|merge([block.ownerId]) %}
{% endfor %}

{% set ownerIds = ownerIds|unique %}

Step 4: Query the Owner Entries

Now fetch the actual entry data for display:

{% set entries = craft.entries()
    .id(ownerIds)
    .all() %}

<h2>Entries using "callToAction" block ({{ entries|length }} found)</h2>
<ul>
{% for entry in entries %}
    <li>
        <a href="{{ entry.cpEditUrl }}">{{ entry.title }}</a>
        (ID: {{ entry.id }}, Section: {{ entry.section.name }})
    </li>
{% endfor %}
</ul>

Complete Working Example

Here's the full template combining all steps:

{# Block Finder - Find all entries using a specific Matrix block type #}

{% set fieldHandle = 'contentBlocks' %}
{% set blockTypeHandle = 'callToAction' %}

{# Get the field ID from the handle #}
{% set field = craft.app.fields.getFieldByHandle(fieldHandle) %}

{% if field %}
    {% set targetBlocks = craft.matrixBlocks()
        .fieldId(field.id)
        .type(blockTypeHandle)
        .all() %}
    
    {% set ownerIds = [] %}
    {% for block in targetBlocks %}
        {% set ownerIds = ownerIds|merge([block.ownerId]) %}
    {% endfor %}
    
    {% set ownerIds = ownerIds|unique %}
    
    {% if ownerIds|length %}
        {% set entries = craft.entries()
            .id(ownerIds)
            .all() %}
        
        <h2>Entries using "{{ blockTypeHandle }}" ({{ entries|length }} found)</h2>
        <table>
            <thead>
                <tr>
                    <th>Title</th>
                    <th>Section</th>
                    <th>ID</th>
                    <th>Edit</th>
                </tr>
            </thead>
            <tbody>
            {% for entry in entries %}
                <tr>
                    <td>{{ entry.title }}</td>
                    <td>{{ entry.section.name }}</td>
                    <td>{{ entry.id }}</td>
                    <td><a href="{{ entry.cpEditUrl }}">Edit</a></td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    {% else %}
        <p>No entries found using the "{{ blockTypeHandle }}" block type.</p>
    {% endif %}
{% else %}
    <p>Field "{{ fieldHandle }}" not found.</p>
{% endif %}

Alternative: Simpler Loop Approach

If you prefer a more straightforward approach that doesn't require knowing the field ID:

{% set entries = craft.entries.section('blog').all() %}

{% for entry in entries %}
    {% for block in entry.contentBlocks.all() %}
        {% if block.type.handle == 'callToAction' %}
            <p>{{ entry.title }} (ID: {{ entry.id }}) uses this block</p>
            {% break %}
        {% endif %}
    {% endfor %}
{% endfor %}

This approach is easier to read but less performant on large sites because it loads all entries and all their blocks.

Considerations for Craft 5

Craft 5 introduced a significant architectural change: Matrix blocks were converted to entries during the upgrade. Matrix fields now contain nested entries with entry types rather than the traditional "block types." Understanding Craft 5 Matrix blocks migration is essential if you're upgrading from earlier versions.

This means the craft.matrixBlocks() query approach from Craft 3/4 may not work the same way. Instead, you'll query nested entries and filter by entry type.

Our experience shows that the migration path varies depending on how your project was upgraded. The core concept remains the same, though: query the nested elements by type, then map back to their owners. Check the Craft 5 documentation for the current query syntax that matches your setup.

Common Mistakes to Avoid

Forgetting About Drafts and Revisions

By default, Matrix block queries in Craft 4.x include parameters like allowOwnerDrafts and allowOwnerRevisions. If you only want to find usage in published content, you may need to explicitly exclude drafts:

{% set targetBlocks = craft.matrixBlocks()
    .fieldId(field.id)
    .type(blockTypeHandle)
    .allowOwnerDrafts(false)
    .allowOwnerRevisions(false)
    .all() %}

Multi-Site Duplicate Results

On multi-site installations, the same logical entry exists across sites with different site-specific versions. Without proper scoping, your results may include duplicates. Add site constraints if you need site-specific results:

{% set targetBlocks = craft.matrixBlocks()
    .fieldId(field.id)
    .type(blockTypeHandle)
    .siteId(currentSite.id)
    .all() %}

Running on Production Without Pagination

On sites with thousands of entries, loading all blocks and entries at once can cause memory issues or timeouts. For large datasets, consider:

  • Running the query as a console command instead of a web request
  • Adding pagination to process blocks in batches
  • Using the database query approach for raw counts

Checking the Wrong Field

If you have multiple Matrix fields with similar block type handles, make sure you're querying the correct field. Always specify the fieldId parameter to avoid confusion.

Testing and Verification

After running your diagnostic template:

1. Spot-check results: Open 3-5 of the reported entries in the control panel and verify they actually contain the block type you searched for.

2. Check the count: If you expected roughly 50 entries to use this block type and the query returns 3, something's likely wrong with your field handle or block type handle.

3. Test with a known entry: Find an entry you know uses the target block type. Run your query and confirm that entry appears in the results.

4. Verify empty results: If the query returns no results, double-check your field and block type handles. Typos are the most common cause of empty result sets.

Plugin Alternatives

If you prefer not to write custom queries, a few plugins can help:

  • Craft Inspector and similar audit plugins can visualize field usage across your content
  • Related Elements provides reverse-lookup functionality for element relationships

These plugins won't necessarily give you a direct "find entries by block type" feature, but they can help with content auditing workflows.

Summary

Finding entries by Matrix block type requires a reverse lookup: query the blocks by type first, then identify their parent entries. The approach works reliably in Craft 3 and 4, while Craft 5's architectural changes require adapting to the new nested entries model.

Working with teams has taught us that running this kind of audit before any Matrix field refactoring saves hours of debugging later. The few minutes spent verifying block usage prevents the "something broke and we don't know why" situations that derail projects.

If you're planning a significant Matrix field refactoring or preparing for a Craft 5 migration, having accurate data about your current content structure matters. We regularly help teams audit their Craft CMS installations and plan content model changes that don't break existing pages. Reach out if you'd like help mapping your current Matrix usage or planning your migration approach.

Share this article