Programmatically output pages from collection

Context

I have a local jekyll instance to manage my poetry, instead of dealing with a WYSIWYG editor. The poems are technically a separate jekyll project that I symlink to other jekyll projects (a submission manager, a personal website, my first book). I specify _poems as a Collection in each config file.

Goal

I’m trying to specify pages from the _poems collection to access in a new page. But I can’t figure out how to get jekyll to access the collection programmatically. I can hardcode the paths with an assign + for loop, but this is tedious & error-prone.

Details

I want to specify the file names from the _poems collection so that I can loop through them in a file like this:

---
author: "marmalamuc"
title: "Submission example"
date: 2025-06-29
layout: default
permalink: example.html
poems:
  - poem1.html
  - poem2.html
  - poem3.html
---
<p>{% include contactInfo.md %}</p>

<p id="journal">{{ page.title }}<br>
{{ page.date | date: "%d %b %Y" }}</p>

<h2>Contents</h2>
<ul>
  {% for item in page.poems %}
  <li><a href="#poem{% increment counter1 %}">{{item.title}}</a></li>
  {% endfor %}
</ul>

{% for item in page.poems %}
  <div class="page-break" id="poem{% increment counter2 %}">
    <h3>{{ item.title }}</h3>
    {% if item.epigraph %}<p class="epigraph">{{item.epigraph}}</p>{% endif %}
    {{ item.content }}
    {% if item.dedication %}<p class="dedication">{{item.dedication}}</p>{% endif %}
  </div>
{% endfor %}

But I can’t figure out how to get jekyll to access the poems. The output is always blank (though it recognizes the correct number of items in the ul & page breaks). I’ve tried all kinds of things with assign, capture, cycle, but can’t get it to work if I specify the files as a group. For example, this (& several variations) does not work:

{% for poem in page.poems %}
  {% assign p = site.poems | where: "permalink", "{{poem}}" %}
  <div class="page-break" id="poem{% increment var %}">
    <h3>{{ p.title }}</h3>
    {% if p.epigraph %}<p class="epigraph">{{p.epigraph}}</p>{% endif %}
    {{ p.content }}
    {% if p.dedication %}<p class="dedication">{{p.dedication}}</p>{% endif %}
  </div>
{% endfor %}

Everything does work if I hardcode a variable for each file (my current approach), or based on a frontmatter property in the poems, then loop through to grab the title & content; i.e.:

  • {% assign p1 = site.poems | where: "permalink", "poem1.html" %}
  • {% assign ode = site.poems | where: "series", "ode" %}

But the former is the tedious approach I’m trying to get away from, and the latter is not useful for creating submissions.

This is so simple in theory, but I’ve been banging my head against a wall for months trying to get it right.

Any ideas?

1 Like

One problem is the quotes and liquid-braces around "{{poem}}" in the where filter. That filters on the literal string “{{poem}}”, not on the value of the poem variable.

Mismatch: where: "permalink", "{{poem}}"
Correct: where: "permalink", poem

1 Like

Thanks for taking a look! I had tried it without the brackets but still in quotes, but not as you suggested. Unfortunately the result is the same

1 Like

The are a couple possibilities. One issue may be that page URLs/permalinks would typically start with a slash (e.g. /poem1.html), so the page.poems list items wouldn’t match the permalinks because they have no slash.

I think the best first debugging step is to see exactly what data is in site.poems. The Liquid inspect filter will output all an objects fields, so you can see exactly what you can filter. For example, to see the first poem, put this on the page (the <pre> is for readability):

<pre>{{ site.poems[0] | inspect }}</pre>

That should output something like this (made-up data):

{
  "url": "/poem1.html",
  "name": "poem1.markdown",
  "path": "_poems/poem1.markdown",
  "permalink": "/poem1.html",
  "title": "....",
  .... 

Now you can adjust the where filter and page.poems list to match the permalink or other fields.

1 Like

Wow, I didn’t know about the inspect filter. That’s so useful.

I’m slightly more confused, because inspect is outputting all the yaml key:value pairs for the items I specify, so it’s definitely accessing the collection.

I’ll keep experimenting, and hopefully I’ll find the right syntax. Thanks!

Another issue here is that the resulting p is a filtered list, not an individual poem, so references like p.title will return nothing. You could explicitly dereference the list’s first item (p[0].title), but I think a cleaner way is to use the first filter to get the first (and presumably only) item from the list:

{% assign p = site.poems | where: "permalink", poem | first %}

After that p.title should work (use p | inspect if it doesn’t).

1 Like

Ah ha! The first filter was the solution for me.

I’m still a little confused because this solution looks the same to me as what I’d been doing—creating a variable for each poem: {% assign p1 = site.poems | where: "permalink", "poem1.html" %}

For posterity, here’s the setup I use to loop through items from a collection instead of hardcoding individual items:

---
author: "marmalamuc"
title: "Submission example"
date: 2025-06-29
layout: default
permalink: example.html
poems:
  - poem1.html
  - poem2.html
  - poem3.html
---
<h2>Contents</h2>
<ul>
  {% for poem in page.poems %}
    {% assign p = site.poems | where: "permalink", poem | first %}
    <li><a href="#poem{% increment counter1 %}">{{item.title}}</a></li>
  {% endfor %}
</ul>

{% for poem in page.poems %}
  {% assign p = site.poems | where: "permalink", poem | first %}
  <div class="page-break" id="poem{% increment counter2 %}">
    <h3>{{ item.title }}</h3>
    {% if item.epigraph %}<p class="epigraph">{{item.epigraph}}</p>{% endif %}
    {{ item.content }}
    {% if item.dedication %}<p class="dedication">{{item.dedication}}</p>{% endif %}
  </div>
{% endfor %}

Grateful for your time, @chuckhoupt!

1 Like