I have a list of students and can sort them by name, sport, and school. The name and sport portions are easy, I'm grabbing the values without issue. Where I'm struggling is the school value is from a Dropdown field. Using the following code results in no students being listed let alone being sorted by school.

   {% set usersSchoolQuery = craft.users().group('athlete').all() %}
   {% set usersBySchool = usersSchoolQuery|group('schoolAttended.one()') %}
   {% for users in usersBySchool.all() %}
   {% for user in users %} 

This should work but isn't. Could someone see where I'm going wrong here?

  • Maybe ignore my "answer" below, for a moment. I relied too much on the code snippet, which implies your schoolAttended field is a relational field (as though Schools were Entries). If it's indeed a Dropdown field with a static set of options, then you should be able to just do craft.users().group('athlete').order('schoolAttended'). Keep in mind that grouping and ordering have different effects! Aug 15, 2020 at 21:23

2 Answers 2


Edit: I've misunderstood the question! It sounds like Schools aren't related Elements, but just a dropdown value?

I think there are a couple of missteps, here:

  1. The .group() method on a UserQuery actually applies a filter, narrowing the results by a User Group with that handle. (Maybe that's understood, but I wanted to note the difference between this and the next couple of notes!)
  2. (An aside that will help optimize this for #3) I recommend eager-loading your related School elements by calling .with(['schoolAttended']) on your main User query.
  3. The | group(…) filter takes an array and groups it by a property, closure, or Twig expression. In this case, I think you actually want to be more explicit about how the grouping is happening, so I'd use | group(u => (u.schoolAttended | first).id). This ensures the key used for grouping is consistent and unique.
  4. usersBySchool.all() is calling the .all() method on an array, which won't work (it'll only be present on ElementQuery instances)—your Elements are already loaded after the first statement, and grouping just takes the resulting array and re-organizes it! This for loop should probably just be: {% for schoolId, athletes in usersBySchool %} … {% endfor %}.

So, altogether:

{# Load all “athlete” users: #}
{% set athletes = craft.users({
  group: 'athlete',
  with: ['schoolAttended']
}).all() %}

{# Group by their School's ID: #}
{% set athletesBySchoolId = athletes | group((u) => (u.schoolAttended | first).id) %}

{# Fetch Schools and index by ID. Note that we have the IDs ready, via the grouping operation, above—we can just take the keys off that array! #}
{% set schoolsById = craft.entries({
  section: 'schools',
  indexBy: 'id',
  id: athletesBySchoolId | keys
}).all() %}

{% for schoolId, athletes in athletesBySchoolId %}
  {# Look up the school for this group via our secondary Schools query (which, again, is indexed by ID): #}
  {% set school = schoolsById[schoolId] %}

  <h1>{{ school.title }}</h1>
    {% for athlete in athletes %}
      <li>{{ athlete.getFullName() }}</li>
    {% endfor %}
{% endfor %} 
  • I'm using a dropdown for schools to eliminate rogue values such as alternate spellings or incorrect names. Trying to figure out how to output a list of students grouped by school. The school value is held in a dropdown. Hope that clarifies somewhat. Also, appreciate the directional input ... thank you! Aug 15, 2020 at 22:23
  • @CynderGray I'll submit a new answer, then. 😉 Aug 15, 2020 at 22:46

Apologies to the community for doubling-up on Answers. OP clarified that indeed they were looking to group based on a Dropdown field, which changes the approach somewhat (and side-steps any eager-loading/nested querying).

Let's see if we can't simplify things. The most important thing to keep in mind in this model is that Dropdown values are stored as simple strings in the database, then “hydrated” with the context of the Dropdown field (i.e. the corresponding Label for that option's value), when populating an Element.

{# Load all “athlete” users: #}
{% set athletes = craft.users({
  group: 'athlete'
}).all() %}

{# Group by School dropdown `value` (I'm interjecting a preference here, which is to use a closure for grouping, instead of letting the Field get automatically coerced—which in this case actually results in the keys human-readable labels, not the actual stored values): #}
{% athletesBySchool = athletes | group((a) => a.schoolAttended.value) %}

{# Get School options from the Field definition, indexing by `value`: #}
{% set schools = craft.app.fields.getFieldByHandle('schoolAttended').options | index('value') %}

{# This results in a structure like this:
     schoolName: {
       value: 'schoolName',
       label: 'School Name'

{% for schoolKey, athletes in athletesBySchool %}
  {# Look up the school for this group among our field's options: #}
  {% set school = schools[schoolKey] %}

  <h1>{{ school.label }}</h1>
    {% for athlete in athletes %}
      <li>{{ athlete.getFullName() }}</li>
    {% endfor %}
{% endfor %} 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.