Element Queries

You can fetch elements (entries, categories, assets, etc.) in your templates or PHP code using element queries.

Suppose you’ve already created a section for news posts and configured a URL scheme. Craft will automatically load the corresponding element when its URL is requested, and pass it to the template under an entry variable. This is convenient, but it’s rare that a page only refers to a single piece of content—what if we want to show a list of other recent posts, in a sidebar? Element queries are Craft’s way of loading elements anywhere you need them.

Element queries can be hyper-specific (like loading a global set by its handle) or relaxed (like a list of recently-updated entries).

Working with element queries consists of three steps:

  1. Create the element query. Calling the “factory function” corresponding to the element type you want to fetch. For entries, this is craft.entries(); for categories, craft.categories().
  2. Set some parameters. By default, element queries will be configured to return all elements of the specified type. You can narrow that down to just the elements you care about by setting parameters on the query.
  3. Execute the query. Use a query execution method to run the query and return results.

Relational fields also return element queries, which you can treat the same as step #1, above.

Here’s what a this process looks like, in practice:

{# Create an entry query and set some parameters on it #}
{% set entryQuery = craft.entries()
  .orderBy('postDate DESC')
  .limit(10) %}

{# Execute the query and get the results #}
{% set entries = entryQuery.all() %}

# Types + Parameters

Each type of element has its own function for creating element queries, and they each have their own parameters you can set.

# Element Types

See the query reference section of each element type for more details on working with them:

Asset Queries
{% set assetQuery = craft.assets() %}
Category Queries
{% set categoryQuery = craft.categories() %}
Entry Queries
{% set entryQuery = craft.entries() %}
Global Set Queries
{% set globalQuery = craft.globals() %}
Matrix Block Queries
{% set matrixBlockQuery = craft.matrixBlocks() %}
Tag Queries
{% set tagQuery = craft.tags() %}
User Queries
{% set userQuery = craft.users() %}

# Parameters

Parameters are set using methods after creating an element query, or by passing in key-value pairs to the factory function:

{% set images = craft.assets()
  .all() %}

{# ...or... #}

{% set images = craft.assets({
  kind: 'image',
}).all() %}

{# ...or if you’re fancy, set some parameters conditionally: #}

{% set imagesQuery = craft.assets() %}

{% if craft.app.request.getParam('onlyImages') %}
  {# Effectively the same as chaining query methods: #}
  {% do imagesQuery.kind('image') %}
{% endif %}

{% set images = imagesQuery.all() %}

Query methods (except for those that execute a query) modify some internal properties and return the query itself, allowing you to chained more methods together—just like Craft’s fluent config syntax!

All element queries support a standard set of methods (like .id(), .title(), and .search()). These are documented alongside the element type-specific parameters (like .kind() in the example above).

Typically, parameters make a query more specific, but setting a single parameter more than once will replace the previous constraint.

# Querying with Custom Fields

In addition to native query parameters, Craft automatically injects a methods for each of your custom fields.

For example, if we wanted to find entries in a Cars section with a specific paint color stored in a dropdown field, we could perform this query:

{% set silverCars = craft.entries()
  .all() %}

Custom field parameters can be combined for advanced filtering—in this example, we’re also applying a pair of constraints to a date field:

{% set silverCars = craft.entries()
  .paintColor(['silver', 'gold'])
  .modelYear('>= 1990', '<= 2000')
  .all() %}

See each field type’s documentation for what kinds of values you can use.

# Executing Element Queries

Once you’ve defined your parameters on the query, there are multiple functions available to execute it, depending on what you need back.

Craft also makes it easy to display the results of an element query across multiple pages with pagination.

# all()

The most common way to fetch a list of results is with the all() method, which executes the query and populates the appropriate element models.

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

Declaring a limit and executing a query with all() may seem like a contradiction, but this is a totally valid query; it will return any records matching the current criteria, without applying any further limits.

# collect()

Calling .collect() to execute a query will perform the same database call as .all(), but the results are wrapped in a collection (opens new window).

Collections can simplify some common array manipulation and filtering tasks that are otherwise awkward in the template:

{% set entries = craft.entries()
  .collect() %}

{% set categoriesDescription = entries
  .join(', ', ' and ') %}
Posted in: {{ categoriesDescription }}

You can also call .all() and wrap the results in a collection yourself, with the collect() Twig function.

# one()

If you only need a single element, call one() instead of all(). It will either return the element or null if no matching element exists.

{% set entry = craft.entries()
  .one() %}

# exists()

If you just need to check if any elements exist that match the element query, you can call exists(), which will return either true or false.

{% set exists = craft.entries()
  .exists() %}

# count()

If you want to know how many elements match your element query, you can call count().

{% set count = craft.entries()
  .count() %}

The limit and offset parameters will be ignored when you call count().

# ids()

If you just want a list of matching element IDs, you can call ids().

{% set entryIds = craft.entries()
  .ids() %}

# column()

Combined with a single-column selection, the column() execution method will return a scalar value for each row instead of an object:

{% set entries = craft.entries()
  .column() %}

{# -> ['Post A', 'Post B', 'Post C'] #}

As a result of the rows being plain values, regular element methods and properties are not available!

# Pagination

Craft provides the {% paginate %} tag to simplify the process of splitting results into pages with a stable URL scheme based on the pageTrigger setting.

The paginate tag accepts an element query, sets its offset param based on the current page, and executes it. The number of results per page is determined by the query’s limit param, or defaults to 100.

{# Prepare your query, but don’t execute it: #}
{% set newsQuery = craft.entries()
  .orderBy('postDate DESC') %}

{# Paginate the query into a `posts` variable: #}
{% paginate newsQuery as pageInfo, posts %}

{# Use the `posts` variable just like you would any other result set: #}
{% for post in posts %}
    <h2>{{ post.title }}</h2>
    {# ... #}
{% endfor %}

Paginating a query will only work if the results come back in a stable order and the page size is kept consistent. Using randomized values in query params or in an orderBy clause will be disorienting for users.

Results from a search query are perfectly fine to paginate.

In our example, the pageInfo variable (a Paginate (opens new window) instance) has a number of properties and methods to help you work with paginated results. The variable can be named anything you like, so long as references to it are updated.

Number of the first element on the current page. For example, on the second page of 10 results, first would be 11.
Number of the last element on the current page. For example, on the first page of 10 results, last would be 10.
Total number of results, across all pages.
The current page. Equivalent to craft.app.request.getPageNum().
The total number of pages the results are spread across. The last page of results may not be complete.
Builds a URL for the specified page of results.
Builds a URL for the first page of results. Equivalent to pageInfo.getPageUrl(1).
Builds a URL for the last page of results. Equivalent to pageInfo.getPageUrl(pageInfo.totalPages).
Get a URL for the next page of results. Returns null on the last page of results.
Get a URL for the previous page of results. Returns null on the first page of results.
Gets up to num next page URLs, indexed by their page numbers.
Gets up to num previous page URLs, indexed by their page numbers.
getRangeUrls(start, end)
Returns a list of URLs indexed by their page number. The list will only include valid pages, ignoring out-of-range start and end values.
Returns up to max page URLs around the current page, indexed by their page numbers.

The values above use a one-based index, so they are human-readable without any additional work.

# Examples

You can display a summary of the current page using pageInfo.total, pageInfo.first, and pageInfo.last:

Showing {{ pageInfo.first }}{{ pageInfo.last }} of {{ pageInfo.total }} results.

Next and previous links are simple:

<nav role="navigation" aria-label="Search result pagination">
  {% if pageInfo.getPrevUrl() %}
    <a href="{{ pageInfo.getPrevUrl() }}">Previous Page</a>
  {% endif %}
  {% if pageInfo.getNextUrl() %}
    <a href="{{ pageInfo.getNextUrl() }}">Next Page</a>
  {% endif %}

We could improve this for screen readers by including specific page numbers in the labels:

{% set prevLinkSummary = "#{pageInfo.currentPage - 1} of #{pageInfo.totalPages}" %}

{{ tag('a', {
  text: 'Previous Page',
  href: pageInfo.getPrevUrl(),
  aria: {
    label: "Previous page (#{prevLinkSummary})"
}) }}

We’re using the tag() Twig function to make this a little more readable, but its output is equivalent to a normal anchor element.

More advanced pagination links are also possible with getDynamicRangeUrls():


<nav role="navigation" aria-label="Search result pagination">
    {% for p, url in pageInfo.getDynamicRangeUrls(5) %}
        {{ tag('a', {
          text: p,
          href: url,
          aria: {
            label: "Go to page #{p} of #{pageInfo.totalPages}",
        }) }}
    {% endfor %}

Notice how our loop uses the keys (p) and values (url) from the returned array—Craft assigns each URL to a key matching its page number!

See the main article on Craft’s search system to learn about the supported syntaxes for plain-text search.

# Performance and Optimization

When you start working with lots of data,

# Eager Loading

Displaying a list of elements and one or more related elements can lead to an “N+1 (opens new window)” problem, wherein each item triggers an additional query.

Eager loading is

# Caching Element Queries

Results can be cached with the cache() method:

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

This cache is separate from fragments cached via {% cache %} template tags, and will only match subsequent queries that have all the same parameters. Caching a query does not guarantee better performance, but it can be used strategically—say, to memoize a scalar query result inside a loop (like the total number of entries in a list of categories).

The cache() method accepts a duration argument, and defaults to your cacheDuration.

Craft registers an ElementQueryTagDependency (opens new window) for you by default, so cache dependencies and invalidation are handled automatically.

# Large Result Sets

Sometimes, a query will simply depend on a large number of elements (and pagination is not possible), or it needs to use the most current data available (so caching is off the table).

Populating element models can be resource-intensive, and loading many thousands of records can exhaust PHP’s memory limit. Let’s look at some common places where queries can be optimized to avoid this bottleneck.

# Counting

In this example, we just need the number of active users:

{# Loads and populates all users, then gets the length of the array: #}
{% set totalUsers = craft.users().status('active').all()|length %}

In addition to the memory footprint of the optimized query being many orders of magnitude smaller, we’re also avoiding a huge amount of data transfer between the PHP process and database server!

Using the length filter on a query (before it’s been run) will automatically call its count() execution method to prevent inadvertent performance issues. Other situations in which queries are treated as arrays may not be optimized in the same way.

# Arithmetic Operations

Counting isn’t the only operation that the database can do for you! What if we wanted to find the minimum and maximum values for a given field?

{# Loads field data for every race, then throws out all but one property: #}
{% set races = craft.entries()
  .all() %}
{% set fastestTime = min(races|column('winningTime')) %}
{% set slowestTime = max(races|column('winningTime')) %}

scalar() is just an execution method that returns the first column from the first result—it will always produce a simple, “scalar (opens new window)” value.

While select() and orderBy() accept field handles and ambiguous columns, some SQL functions and expressions (like MIN() or SUM()) do not.

In these cases, you may need to use craft\helpers\ElementHelper::fieldColumnFromField() (opens new window) in combination with craft\services\Fields::getFieldByHandle() (opens new window) to translate a field handle to a content table column name.

# Lean Selections

Combining a narrower selection with an execution method that returns results as an array (or explicitly calling toArray() while preparing a query) can significantly reduce the amount of memory a query requires.

{# Load all donor entries with complete native + custom field data: #}
{% set donors = craft.entries()
  .all() %}

  {% for donor in donors %}
    <li>{{ donor.title }}{{ donor.lifetimeGiftAmount|money }}</li>
  {% endfor %}

The pairs() execution method is a shorthand for creating a key-value hash from the first two columns of each row. Collisions can occur, so it’s safest to use a column you know will be unique for your first selection!

Not all attributes can be fetched this way—element URLs, for instance, are built on-the-fly from their URIs and site’s base URL. Relational data may also be more difficult to work with, as it often has to be eager-loaded alongside fully-populated element models.

# Advanced Element Queries

Element queries are specialized query builders (opens new window) under the hood, so they support most of the same methods provided by craft\db\Query (opens new window).

# Selections

Custom field column names will be automatically resolved when using select(). 4.3.0+ In earlier versions, you may find that some field’s database columns include a random suffix and will require translating the field handle with craft\helpers\ElementHelper::fieldColumnFromField() (opens new window).

# Joins

# Conditions

Exercise caution when using these methods directly—some will completely overwrite the existing query conditions and cause unpredictable results.

Specific element type queries and custom field methods often provide a more approachable and reliable API for working with the database.

# Query Execution

Some of these methods are discussed in the Executing Element Queries section.

When customizing an element query, you can call getRawSql() (opens new window) to get the full SQL that is going to be executed by the query, so you have a better idea of what to modify.

{{ dump(query.getRawSql()) }}

# Headless Applications

Craft can act as a headless content back-end for your static or client-rendered website. There are two main ways of making content available to applications that exist outside Craft’s built-in Twig templating layer:

# Element API

The first-party Element API (opens new window) allows you to map endpoints to element queries with a combination of static and dynamic criteria and serve JSON-serialized results.

# GraphQL Pro

Craft Pro includes a GraphQL API with configurable schemas.

For security reasons, not all query builder features are available via GraphQL. Some advanced queries may need to be executed separately and combined by the client.