Radical Design Course by Jack McDade

From the creator of Statamic

Learn how to make your websites standout and be remembered.

Just exceptional. Thank you so much, Jack, you smashed it.

— Hugo, Developer

Collection Tag

Entries are grouped into Collections and are fetched and filtered by this tag. A Collection could contain blog posts, products, or even a bag full of dad jokes. We don't judge, and neither does the Collection Tag.

Overview

The collection tag is one of main workhorses of your Statamic frontend. It's like an Eloquent model in Laravel or "The Loop" in WordPress – it's how you get data from everywhere (other than the current entry and global variables) into your view.

Example

A basic example would be to loop through the entries in a blog collection and link to each individual blog post:

<ul>
{{ collection from="blog" }}
<li><a href="{{ url }}">{{ title }}</a></li>
{{ /collection }}
</ul>
<ul>
<statamic:collection
from="blog"
>
<li><a href="{{ $url }}">{{ $title }}</a></li>
</statamic:collection>
</ul>

You can also use the shorthand syntax for this. We prefer this style ourselves.

<ul>
{{ collection:blog }}
<li><a href="{{ url }}">{{ title }}</a></li>
{{ /collection:blog }}
</ul>
<ul>
<statamic:collection:blog>
<li><a href="{{ $url }}">{{ $title }}</a></li>
</statamic:collection:blog>
</ul>

If you'd like to fetch entries from multiple collections, you'll need to use the standard syntax.

{{ collection from="blog|events" }}
<statamic:collection
from="blog|events"
>
 
</statamic:collection>

To get entries from all collections, use the wildcard *. You may also exclude collections when doing this.

{{ collection from="*" not_from="blog|events" }}
<statamic:collection
from="*"
not_from="blog|events"
>
 
</statamic:collection>

Filtering

There are a number of ways to filter your collection. There's the conditions syntax for filtering by fields, taxonomy filter for using terms, and the custom filter class if you need extra control.

Conditions

Want to get entries where the title has the words "awesome" and "thing", and "joe" is the author? You can write it how you'd say it:

{{ collection:blog title:contains="awesome" title:contains="thing" author:is="joe" }}
<statamic:collection:blog
title:contains="awesome"
title:contains="thing"
author:is="joe"
>
 
</statamic:collection:blog>

There are a bunch of conditions available to you, like :is, :isnt, :contains, :starts_with, and :is_before. There are many more than that. In fact, there's a whole page dedicated to conditions - check them out.

Taxonomies

Filtering by a taxonomy term (or terms) is done using the taxonomy parameter, similar to the conditions syntax mentioned above.

To show entries with the harry-potter term within the tags taxonomy, you could do this:

{{ collection:blog taxonomy:tags="harry-potter" }}
<statamic:collection:blog
taxonomy:tags="harry-potter"
>
 
</statamic:collection:blog>

It is important that the collection has been configured to use this taxonomy in order to filter the results based on the passed in term.

Hot Tip!

There are several different ways to use this filtering parameter. They are explained in depth on the Conditions page.

Published Status

By default, only published entries are included. Entries can be queried against any, draft, scheduled, or expired status with conditions on status like this:

// Only include published entries
{{ collection:blog status:is="published" }}
<statamic:collection:blog
status:is="published"
>
 
</statamic:collection:blog>
Hot Tip!

What is the difference between filtering against published and status? Read more on date behavior and published status!

Custom Query Scopes

Doing something custom or complicated? You can create query scopes to narrow down those results with the query_scope or filter parameter:

{{ collection:blog query_scope="your_query_scope" }}
<statamic:collection:blog
query_scope="your_query_scope"
>
 
</statamic:collection:blog>

You should reference the query scope by its handle, which is usually the name of the class in snake case. For example: YourQueryScope would be your_query_scope.

Pagination

To enable pagination mode, add the paginate parameter with the number of entries in each page.

{{ collection:blog paginate="10" as="posts" }}
 
{{ if no_results }}
<p>Aww, there are no results.</p>
{{ /if }}
 
{{ posts }}
<article>
{{ title }}
</article>
{{ /posts }}
 
{{ paginate }}
<a href="{{ prev_page }}">⬅ Previous</a>
 
{{ current_page }} of {{ total_pages }} pages
(There are {{ total_items }} posts)
 
<a href="{{ next_page }}">Next ➡</a>
{{ /paginate }}
 
{{ /collection:blog }}
<statamic:collection:pages
paginate="10"
as="posts"
>
@if ($no_results)
<p>Aww, there are no results.</p>
@endif
 
@foreach ($posts as $post)
<article>
{{ $post->title }}
</article>
@endforeach
 
@if ($paginate['total_pages'] > 1)
<a href="{{ $paginate['prev_page'] }}">⬅ Previous</a>
 
{{ $paginate['current_page'] }} of {{ $paginate['total_pages'] }} pages
(There are {{ $paginate['total_items'] }} posts)
 
<a href="{{ $paginate['next_page'] }}">Next ➡</a>
@endif
</statamic:collection:pages>

In pagination mode, your entries will be scoped (in the example, we're scoping them into the posts tag pair). Use that tag pair to loop over the entries in that page.

The paginate variable will become available to you. This is an array containing data about the paginated set.

Variable Description
next_page The URL to the next paginated page.
prev_page The URL to the previous paginated page.
total_items The total number of entries.
total_pages The number of paginated pages.
current_page The current paginated page. (ie. the x in the ?page=x param)
auto_links Outputs an HTML list of paginated links.
links Contains data for you to construct a custom list of links.
links:all An array of all the pages. You can loop over this and output {{ url }} and {{ page }}.
links:segments An array of data for you to create a segmented list of links.

Pagination Examples

The auto_links tag is designed to be your friend. It'll save you more than a few keystrokes, and even more headaches. It will output an HTML list of links for you. With a large number of pages, it will create segments so that you don't end up with hundreds of numbers.

It's clever enough to work out a comfortable range of numbers to display, and it'll also throw in the prev/next arrow for good measure.

Maybe the default markup isn't for you and you want total control. You're a maverick. That's cool, we roll that way sometimes too. That's where the links:all or links:segments array variables come in. These give you all the data you need to recreate your own set of links.

  • The links:all array is all the pages with url and page variables.

  • The links:segments array will contain the segments mentioned above. You'll be able to access first, slider, and last, which are the 3 segments.

Here's the auto_links output, recreated using the other tags, for you mavericks out there:

{{ paginate }}
<ul class="pagination">
{{ if prev_page }}
<li><a href="{{ prev_page }}">&laquo;</a></li>
{{ else }}
<li class="disabled"><span>&laquo;</span></li>
{{ /if }}
 
{{ links:segments }}
 
{{ first }}
{{ if page == current_page }}
<li class="active"><span>{{ page }}</span></li>
{{ else }}
<li><a href="{{ url }}">{{ page }}</a></li>
{{ /if }}
{{ /first }}
 
{{ if slider }}
<li class="disabled"><span>...</span></li>
{{ /if }}
 
{{ slider }}
{{ if page == current_page }}
<li class="active"><span>{{ page }}</span></li>
{{ else }}
<li><a href="{{ url }}">{{ page }}</a></li>
{{ /if }}
{{ /slider }}
 
{{ if slider || (!slider && last) }}
<li class="disabled"><span>...</span></li>
{{ /if }}
 
{{ last }}
{{ if page == current_page }}
<li class="active"><span>{{ page }}</span></li>
{{ else }}
<li><a href="{{ url }}">{{ page }}</a></li>
{{ /if }}
{{ /last }}
 
{{ /links:segments }}
 
{{ if next_page }}
<li><a href="{{ next_page }}">&raquo;</a></li>
{{ else }}
<li class="disabled"><span>&raquo;</span></li>
{{ /if }}
</ul>
{{ /paginate }}
<statamic:collection:blog
paginate="10"
as="posts"
>
@if ($no_results)
<p>Aww, there are no results.</p>
@endif
 
@foreach ($posts as $post)
<article>
{{ $post->title }}
</article>
@endforeach
 
@if ($paginate['total_pages'] > 1)
@php
$hasSlider = count($paginate['links']['segments']['slider']) > 0;
$hasLast = count($paginate['links']['segments']['last']) > 0;
@endphp
 
<ul class="pagination">
@if ($paginate['prev_page'])
<li><a href="{{ $paginate['prev_page'] }}">&laquo;</a></li>
@else
<li class="disabled"><span>&laquo;</span></li>
@endif
 
@foreach (Arr::get($paginate, 'links.segments.first', []) as $segment)
@if ($segment['page'] == $paginate['current_page'])
<li class="active"><span>{{ $segment['page'] }}</span></li>
@else
<li><a href="{{ $segment['url'] }}">{{ $segment['page'] }}</a></li>
@endif
@endforeach
 
@if ($hasSlider)
<li class="disabled"><span>...</span></li>
@endif
 
@foreach (Arr::get($paginate, 'links.segments.slider', []) as $segment)
@if ($segment['page'] == $paginate['current_page'])
<li class="active"><span>{{ $segment['page'] }}</span></li>
@else
<li><a href="{{ $segment['url'] }}">{{ $segment['page'] }}</a></li>
@endif
@endforeach
 
@if ($hasSlider || $hasLast)
<li class="disabled"><span>...</span></li>
@endif
 
@foreach (Arr::get($paginate, 'links.segments.last', []) as $segment)
@if ($segment['page'] == $paginate['current_page'])
<li class="active"><span>{{ $segment['page'] }}</span></li>
@else
<li><a href="{{ $segment['url'] }}">{{ $segment['page'] }}</a></li>
@endif
@endforeach
 
@if ($paginate['next_page'])
<li><a href="{{ $paginate['next_page'] }}">&raquo;</a></li>
@else
<li class="disabled"><span>&raquo;</span></li>
@endif
</ul>
@endif
</statamic:collection:blog>

Aliasing

Often times you'd like to have some extra markup around your list of entries, but only if there are results. Like a <ul> element, for example. You can do this by aliasing the results into a new variable tag pair. This actually creates a copy of your data as a new variable. It goes like this:

{{ collection:blog as="posts" }}
<ul>
{{ posts }}
<li>
<a href="{{ url }}">{{ title }}</a>
</li>
{{ /posts }}
<ul>
{{ /collection:blog }}
<collection:blog
as="posts"
>
<ul>
@foreach ($posts as $post)
<li>
<a href="{{ $post->url }}">{{ $post->title }}</a>
</li>
@endforeach
</ul>
</collection:blog>

Scoping

Sometimes not all of your entries have the same set of variables. And sometimes the page that you're on (while listing entries in a Collection, for example) may have those very same variables on the page-level scope. Statamic assumes you'd like to fallback to the parent scope's data to plug any holes. This logic has pros and cons, and you can read more about scoping and the Cascade here.

You can assign a scope prefix to your entries so you can be sure to get the data you want. Define your scope and then prefix all of your variables with it.

# Page data
featured_image: /img/totes-adorbs-kitteh.jpg
{{ collection:blog scope="post" }}
<div class="block">
<img src="{{ post:featured_image }}">
</div>
{{ /collection:blog }}
<collection:blog
scope="post"
>
<div class="block">
<img src="{{ $post->featured_image }}">
</div>
</collection:blog>

You can also add your scope down into your alias loop. Yep, we thought of that too.

{{ collection:blog as="posts" }}
{{ posts scope="post" }}
<div class="block">
<img src="{{ post:featured_image }}">
</div>
{{ /posts }}
{{ /collection:blog }}
<collection:blog
as="posts"
>
@foreach ($posts as $post)
<div class="block">
<img src="{{ $post->featured_image }}">
</div>
@endforeach
</collection:blog>

Combining both an Alias and a Scope on a Collection Tag doesn't make a whole lot of sense. You shouldn't do that.

Grouping

To group entries – by date or any other field – you should use aliasing (explained above) as well as the group_by modifier.
There's no "grouping" feature on the collection tag itself.

For example, if you want to group some dated entries by month/year, you could do this:

{{ collection:articles as="entries" }}
{{ entries group_by="date|F Y" }}
{{ groups }}
<h3>{{ group }}</h3>
{{ items }}
{{ title }} <br>
{{ /items }}
{{ /groups }}
{{ /entries }}
{{ /collection:articles }}
<statamic:collection:articles
as="entries"
>
@php
$groupedEntries = $entries->groupBy(fn($entry) => $entry->date?->format('F Y'));
@endphp
 
@foreach ($groupedEntries as $group => $items)
<h3>{{ $group }}</h3>
 
@foreach ($items as $entry)
{{ $entry->title }}
@endforeach
@endforeach
</statamic:collection:articles>

Parameters

from|folder|use

string|array

The name of the collection(s). Pipe separate names to fetch entries from multiple collections. You may use * to get entries from all collections.

not_from|not_folder|dont_use

string|array

When getting all collections with *, this parameter can accept a pipe delimited list of collections to exclude.

collection

tag part

The name of the collection when using the shorthand syntax. This is not actually a parameter, but part of the tag itself. For example, {{ collection:blog }}.

show_future

boolean *false*

Date-based entries from the future are excluded from results by default. Of course, if you want to show upcoming events or similar content, flip this switch.

show_past

boolean *true*

Just like show_future, but for entries in the past.

since

string/var

Limits the date the earliest point in time from which date-based entries should be fetched. You can use plain English (PHP's strtotime method will interpret. eg. last sunday, january 15th, 2013, yesterday) or the name any date variable.

until

string/var

The inverse of since, but sets the max date.

sort

string

Sort entries by field name (or random). You may pipe-separate multiple fields for sub-sorting and specify sort direction of each field using a colon. For example, sort="title" or sort="date:asc|title:desc" to sort by date then by title. To sort manually, use sort="order". (Make sure to set max depth to 1 for your collection).

limit

integer

Limit the total results returned.

filter|query_scope

string

Apply a custom query scope You should specify the query scope's handle, which is usually the name of the class in snake case. For example: MyAwesomeScope would be my_awesome_scope.

offset

integer

Skip a specified number of results.

taxonomy

mixed

A multitude of ways to filter by taxonomies. More details

paginate

boolean|int *false*

Specify whether your entries should be paginated. You can pass true and also use the limit param, or just pass the limit directly in here.

page_name

string *page*

The query string variable used to store the page number (ie. ?page=).

on_each_side

int *3*

When using pagination, this specifies the max number of links each side of the current page. The minimum value is 1.

as

string

Alias your entries into a new variable loop.

scope

string

Scope your entries with a variable prefix.

locale|site

string

Show the retrieved content in the selected locale.

redirects|links

boolean *false*

By default, entries with redirects will be filtered out. Set this to true to include them.

Variables

Variable Type Description

first

boolean

Is this the first item in the loop?

last

boolean

Is this the last item in the loop?

count

integer

The number/index of current iteration in the loop, starting from 1

index

integer

The number/index of current iteration in the loop, starting from 0

order

integer

The number/index of the item relative to the collection, not affected by any sort/filter parameters on the tag. Note: this is only available on collections where the order is set to number.

no_results

boolean

Returns true if there are no results.

total_results

integer

The total number of results in the loop when there are results. You should use no_results to check if any results exist.

entry data

mixed

Each result has access to all the variables inside that entry (title, content, etc).

HR: Section
Learn More!

Fetches and filters entries in one or more collections.

HR: Section
Docs feedback

Submit improvements, related content, or suggestions through Github.

Betterify this page →