I've built a page listing all entries by Character 0-9/A-Z. But unfortunately it's extremely slow loading due to the amount of entries the site has. I'm wondering if I can break out the letters into seperate pages.

Currently everything is on


What I'm looking to do is create


I'm unsure if it's code running to create this or something more in the backend of Craft and how I've created the section. The code currently running the A-Z

{% set entries = craft.entries.section('brands') %}
      {% set entriesGrouped = entries|group('title|slice(0, 1)|upper|replace("/[^A-Z]/", "0-9")') %}

      {% for letter, entriesInGroup in entriesGrouped %}
          <h3>{{ letter }}</h3>
          <ul class="store-list">
          {% for entry in entriesInGroup %}
              {% for asset in entry.brandLogo.all() %}
                <a href="{{ entry.url }}">
                  <div class="retailer-logo">
                    <img src="{{ asset.geturl }}" class="img-fluid" alt="{{ entry.title }} Discount Codes & Vouchers"/>
              {% endfor %}
              <a href="{{ entry.url }}" title="{{ entry.title }} Coupon Codes">{{ entry.title }} Coupons</a>
              {% endfor %}
{% endfor %}

2 Answers 2


You can create a route with a URL rule for these individual pages:

'brands/<firstLetter:\w{1}>' => ['template' => 'brands/index'],

Assuming you URL is something like example.com/brands/a and your template is located at brands/index.twig. This will also pass a firstLetter variable from the URL to the template.

You can use Craft's search parameter to only search the title field starting with a letter like this:


You can read more about Craft's search syntax here.

For the number 0-9, combine search queries with an OR string like title::0* OR title::1* OR title::2* OR... etc.

You can't use a URL like example.com/brands/# to show numbers, so I'm just going to use the default example.com/brands to show numbered entries (no firstLetter in the URL).

Putting it all together, we get something like this:

{% if firstLetter is defined %}
  {% set searchQuery = 'title::' ~ firstLetter ~ '*' %}
{% else %}
  {% set searchQuery = '' %}
  {% for num in 0..9 %}
    {% set searchQuery = searchQuery ~ 'title::' ~ num ~ '*' ~ (not loop.last ? ' OR ') %}
  {% endfor %}
{% endif %}

{% for entry in craft.entries.section('brands')
    .all() %}
  {{ entry.title }}
{% endfor %}

Here's a simple navigation for these pages.

  <a href="{{ url('brands') }}">#</a>
  {% for letter in 'a'..'z' %}
    <a href="{{ url('brands/' ~ letter) }}">{{ letter|upper }}</a>
  {% endfor %}

To filter out letters from that list that don't have any entries would be trickier to pull off tho. You could try something like this, but it might not be the most performant.

{% for letter in 'a'..'z' 
    if craft.entries.section('brands')
    .search('title::' ~ letter ~ '*')
    .exists() %}

Another possible option would be to create a plugin for finding distinct first letter entries like this person did: https://craftcms.stackexchange.com/a/10957/273

  • The pages are not setup with that at the moment, so it's a channel with all of them listed, would this work still? vouchertoday.uk/stores as you can see these need to be split into different pages.
    – JMKelley
    Sep 19, 2018 at 9:32
  • Yes, the code above is written to pull all the entries from a "brands" channel. You wouldn't need to split your entries up separately in the Craft control panel. For each URL/route like /brands/a or /brands/b it will only display entries where the titles start with the letter in the URLs last segment.
    – Alex Roper
    Sep 19, 2018 at 17:09
  • Sorry Alex, How do you go about splitting up entries in the CP?
    – JMKelley
    Sep 19, 2018 at 19:28
  • 1
    You would NOT have to split them up in the control panel. Do you want to split them up in the control panel for some reason? I thought your question was asking how to display entries from a single channel split up over multiple pages on the front end of the site.
    – Alex Roper
    Sep 19, 2018 at 22:17
  • 1
    Yes, you'll need a template file that corresponds to your URL rule in the config/routes.php file. In the example above ('template' => 'brands/index') that would be templates/brands/index.twig (or .html).
    – Alex Roper
    Sep 23, 2018 at 18:18

You could use eager loading to speed up things as it is now in your template.

{% set entries = craft.entries({
  section: 'brands',
  with:    ['brandLogo']
}.all() %}

Another alternative I can think of is using a structure instead of a channel, with 0-9 and A-Z as top level entries and your brands already sorted per character underneath. Than you can just call up the correct letter/branch in your template. Switching from channel to structure won't erase your data either if I'm correct. And the eager loading trick will speed up things too here.

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.