How would I loop through all categories in a group to get this scenario:

  • Get 4 entries per category
  • No duplicates across any of the categories, if it's in Cat 1, should not show in any other.
  • Category info (title, URL, etc)

So here is a visual:

Category 1

  • Entry 1
  • Entry 2
  • Entry 3
  • Entry 4

Category 2

  • Entry 1 (duplicate from Category 1, do not want here, so get 4 different)
  • Entry 5
  • Entry 6
  • Entry 7
  • Entry 8

Category 3

  • Entry 2 (duplicate from Category 1, do not want here, so get 4 different)
  • Entry 6 (duplicate from Category 2, do not want here, so get 4 different)
  • Entry 9
  • Entry 10
  • Entry 11
  • Entry 12

I hope this gives a decent visual of what I want.

1 Answer 1


I've tested this with some of my own content and believe it works to your spec. You need to keep a track of all entries that have been displayed so far. You then loop through each categories related entries and if the entry hasn't been shown in a previous category we add it to a new array within that category loop.

    // Set empty array to hold id's of all currently displayed entries
    {% set entries_displayed = [] %}

    // Loop over a category group
    {% for category in craft.categories.group( 'productBrand' ) %}
      <h2>{{ category.title }}</h2>

      // Get related entries to the category
      {% set related_entries = craft.entries.relatedTo( category ) %}

      // Set empty array to hold id's of entries we'll eventually output
      {% set entries_to_display = [] %}

      // Loop through related entries
      {% for entry in related_entries %}

        // If the entry hasn't been displayed
        {% if entry.id not in entries_displayed %}

          // We want to show the entry
          {% set entries_to_display = entries_to_display|merge([entry.id]) %}
        {% endif %}
      {% endfor %}

      // Finally grab the entries to display from the array of id's that haven't already been shown
      {% set entries = craft.entries.id( entries_to_display ).limit( 4 ) %}
      {% for entry in entries %}

        // Add the id to our "already displayed" array
        {% set entries_displayed = entries_displayed|merge([entry.id]) %}

        {{ entry.id }} - {{ entry.title }}<br>
      {% endfor %}
    {% endfor %}


Erik later discovered an error in my original answer where I was setting entries_displayed in the first for loop before they'd actually been output in the last for loop. I've updated the code to reflect his fix.

I had to move the entries_displayed array to the last for loop which displayed the entries. That way I knew exactly what entries had been displayed. If I didn't do that, it would assume more than 4 were shown per category.

  • This is so close! For some reason, one category doesn't get any entries. There are 4 categories and the 3rd is the issue. There are no expiration dates and all the entries are live. I can't imagine what the issue may be. Thank you btw!
    – Erik
    Commented Aug 1, 2016 at 18:40
  • Is it in the same category group? Have you checked there are any related entries returned? Commented Aug 2, 2016 at 8:03
  • I believe I figured it out. I will post an update with my code. I moved the entries_displayed array to the last for loop which displays the entries. By doing that, I'm guaranteeing only the ones displayed get merged into that array.
    – Erik
    Commented Aug 2, 2016 at 12:24
  • @Erik - The problem with that is that there are no initial entries to show because entries_to_display is empty. I will test with the limit 4 issue and update my answer. Commented Aug 2, 2016 at 13:15
  • Hmm... it works though. Test my code.
    – Erik
    Commented Aug 2, 2016 at 13:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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