Skip to navigation Skip to main content
Eleventy
Eleventy Documentation
Stable
3.0.0
Toggle Menu
Eleventy 1.93s
Next.js 70.65s

Collections (Using Tags)

Contents

While pagination allows you to iterate over a data set to create multiple templates, a collection allows you to group content in interesting ways. A piece of content can be a part of multiple collections, if you assign the same string value to the tags key in the front matter.

Take care to note that tags have a singular purpose in Eleventy: to construct collections of content. Some blogging platforms use Tags to refer to a hierarchy of labels for the content (e.g. a tag cloud).

A Blog Example

For a blog site, your individual post files may use a tag called post, but it can be whatever you want. In this example, mypost.md has a single tag post:

Syntax Markdown
---
tags: post
title: Hot Take—Social Media is Considered Harmful
---

This will place this mypost.md into the post collection with all other pieces of content sharing the post tag. To reference this collection and make a list of all posts, use the collections object in any template:

<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function render(data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};
exports.render = function (data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};

A note about using - in tags

If you use - in your collection names (e.g. tags: "post-with-dash"), remember that some template languages require square bracket notation to reference it in collections. Read more at Issue #567.

<ul>
{%- for post in collections.post-with-dash -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections['post-with-dash'] -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function render(data) {
return `<ul>
${data.collections['post-with-dash']
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};
exports.render = function (data) {
return `<ul>
${data.collections['post-with-dash']
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};

Declare your collections for incremental builds

Added in v2.0.0Use the eleventyImport object to declare any collections you use (data cascade friendly) to inform the relationships for smarter incremental builds. This is an Array of collection names. Read more about importing collections.

---
eleventyImport:
collections: ["post"]
---
<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
---
eleventyImport:
collections: ["post"]
---
<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function data() {
return {
eleventyImport: {
collections: ["post"],
},
};
};

export function render(data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};
exports.data = function () {
return {
eleventyImport: {
collections: ["post"],
},
};
};

exports.render = function (data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};

Use an [aria-current] attribute on the current page

Compare the post.url and special Eleventy-provided page.url variable to find the current page. Building on the previous example:

<ul>
{%- for post in collections.post -%}
<li{% if page.url == post.url %} aria-current="page"{% endif %}>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections.post -%}
<li{% if page.url == post.url %} aria-current="page"{% endif %}>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function render(data) {
return `<ul>
${data.collections.post
.map(
(post) =>
`<li${data.page.url === post.url ? ` aria-current="page"` : ""}>${
post.data.title
}
</li>`

)
.join("\n")}

</ul>
`
;
};
exports.render = function (data) {
return `<ul>
${data.collections.post
.map(
(post) =>
`<li${data.page.url === post.url ? ` aria-current="page"` : ""}>${
post.data.title
}
</li>`

)
.join("\n")}

</ul>
`
;
};

Background: aria-current="page" tells assistive technology, such as screen readers, which page of a set of pages is the current active one. It also provides a hook for your CSS styling, using its attribute selector: [aria-current="page"] {}.

The Special all Collection

By default Eleventy puts all of your content (independent of whether or not it has any assigned tags) into the collections.all Collection. This allows you to iterate over all of your content inside of a template.

<ul>
{%- for post in collections.all -%}
<li><a href="{{ post.url }}">{{ post.url }}</a></li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections.all -%}
<li><a href="{{ post.url }}">{{ post.url }}</a></li>
{%- endfor -%}
</ul>
export function render(data) {
return `<ul>
${data.collections.all
.map((post) => `<li><a href="${post.url}">${post.url}</a></li>`)
.join("\n")}

</ul>
`
;
};
exports.render = function (data) {
return `<ul>
${data.collections.all
.map((post) => `<li><a href="${post.url}">${post.url}</a></li>`)
.join("\n")}

</ul>
`
;
};

How to Exclude content from Collections

In front matter (or further upstream in the data cascade), set the eleventyExcludeFromCollections option to true to opt out of specific pieces of content added to all collections (including collections.all, collections set using tags, or collections added from the Configuration API in your config file). Useful for your RSS feed, sitemap.xml, custom templated .htaccess files, et cetera.

Filename excluded.md
---
eleventyExcludeFromCollections: true
tags: post

---


This will not be available in `collections.all` or `collections.post`.

Added in v3.0.0 eleventyExcludeFromCollections can now also accept an array of tag names:

---
eleventyExcludeFromCollections: ["post"]
---


This will be available in `collections.all` but not `collections.post`.

Add to a Collection using Tags

You can use a single tag, as in the above example OR you can use any number of tags for the content, using YAML syntax for a list.

A single tag: cat

---
tags: cat
---

This content would show up in the template data inside of collections.cat.

Using multiple words in a single tag

---
tags: cat and dog
---

If you use multiple words for one tag you can access the content by the following syntax collections['cat and dog'].

Multiple tags, single line

---
tags: ["cat", "dog"]
---

This content would show up in the template data inside of collections.cat and collections.dog.

Multiple tags, multiple lines

---
tags:
- cat
- dog

---

This content would show up in the template data inside of collections.cat and collections.dog.

Override tags

As of Eleventy 1.0, the Data Cascade is combined using deep data merge by default, which means tags are merged together with tags assigned higher in the data cascade (the Arrays are combined). To redefine tags in the front matter use the override: prefix:

---
override:tags: []
---

This content would not show up in any of the collections it was added to with tags higher up in the data cascade.

Collection Item Data Structure

<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections.post -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function render(data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};
exports.render = function (data) {
return `<ul>
${data.collections.post
.map((post) => `<li>${post.data.title}</li>`)
.join("\n")}

</ul>
`
;
};

Note in the above example that we output the post.data.title value? Similarly, each collection item will have the following data:

  • page: everything in Eleventy’s supplied page variable for this template (including inputPath, url, date, and others). Added in v2.0.0
  • data: all data for this piece of content (includes any data inherited from layouts)
  • rawInput: the raw input of the template (before any processing). This does not include front matter. Added in v3.0.0 (Related: #1206)
  • content: the rendered content of this template. This does not include layout wrappers. Added in v2.0.0
{
page: {
inputPath: './test1.md',
url: '/test1/',
date: new Date(),
// … and everything else in Eleventy’s `page`
},
data: { title: 'Test Title', tags: ['tag1', 'tag2'], date: 'Last Modified', /* … */ },
content: '<h1>Test Title</h1>\n\n<p>This is text content…',
// Pre-release only: v3.0.0
rawInput: '<h1>{{ title }}</h1>\n\n<p>This is text content…',
}

Backwards compatibility notes:

  • Top level properties for inputPath, fileSlug, outputPath, url, date are still available, though use of page.* Added in v2.0.0 for these is encouraged moving forward.
  • content Added in v2.0.0 is aliased to the previous property templateContent.

You can view the previous Collection Item Data Structure docs for 1.0.

Sorting

The default collection sorting algorithm sorts in ascending order using:

  1. The input file’s Created Date (you can override using date in front matter, as shown below)
  2. Files created at the exact same time are tie-broken using the input file’s full path including filename

For example, assume I only write blog posts on New Years Day:

posts/postA.md (created on 2008-01-01)
posts/postB.md (created on 2008-01-01)
posts/post3.md (created on 2007-01-01)
another-posts/post1.md (created on 2011-01-01)

This collection would be sorted like this:

  1. posts/post3.md
  2. posts/postA.md
  3. posts/postB.md
  4. another-posts/post1.md

Sort descending

To sort descending in your template, you can use a filter to reverse the sort order. For example, it might look like this:

<ul>
{%- for post in collections.post reversed -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
<ul>
{%- for post in collections.post | reverse -%}
<li>{{ post.data.title }}</li>
{%- endfor -%}
</ul>
export function render(data) {
// `toReversed` is Node 20+, see the note below
let posts = data.collections.post.toReversed();
return `<ul>
${posts.map((post) => `<li>${post.data.title}</li>`).join("\n")}
</ul>
`
;
};
exports.render = function (data) {
// `toReversed` is Node 20+, see the note below
let posts = data.collections.post.toReversed();
return `<ul>
${posts.map((post) => `<li>${post.data.title}</li>`).join("\n")}
</ul>
`
;
};

Do not use Array reverse()

WARNING:

You should not use Array reverse() on collection arrays in your templates, like so:

{%- for post in collections.post.reverse() -%}

This will mutate the array and re-order it in-place and will have side effects for any use of that collection in other templates.

This is a Common Pitfall.

This applies any time you use reverse, for example in a custom shortcode:

eleventy.config.js
export default function (eleventyConfig) {
eleventyConfig.addShortcode("myShortcode", function (aCollection){
// WARNING
aCollection.reverse();
})
};
module.exports = function (eleventyConfig) {
eleventyConfig.addShortcode("myShortcode", function (aCollection){
// WARNING
aCollection.reverse();
})
};

Instead of reverse use:

Overriding Content Dates

You can modify how a piece of content is sorted in a collection by changing its default date. Read more at Content Dates.

---
date: 2016-01-01
---

Advanced: Custom Filtering and Sorting

This part of the docs has moved to its own page: Collections API

From the Community

×80 resources courtesy of 11tybundle.dev curated by IndieWeb Avatar for https://www.bobmonsour.com/Bob Monsour

Expand to see 75 more resources.

Other pages in Eleventy Projects: