Publican group indexes

748 words, 4-minute read

You can organise pages into arbitrary groups based on any factor, e.g. featured posts, years, authors, etc. Groups and tags are similar, except:

Groups in front matter #

You can organise pages into groups using front matter that defines one or more comma-delimited names:

---
title: A post
description: Post description
author: Craig Buckler
date: 2025-10-22
groups: featured, new
---

tacs.group #

tacs.group is a global Map object of all published posts in a group. Each item returns an ordered array of posts. For example, show links to all posts in the featured group:

${ tacs.group.get('featured').map(
  p => `<p><a href="${ p.link }">${ p.title }</a></p>`
) }

Group configuration #

The publican.config.groupPages configuration object controls group lists in the Publican configuration file. For example, put all group lists in reverse chronological order:

publican.config.js excerpt

publican.config.groupPages = {
  sortBy: 'date',
  sortOrder: -1,
};

You can configure ordering for specific lists using a child list object. The following code orders the featured group by priority, but uses the default date for other groups:

publican.config.js excerpt

publican.config.groupPages = {
  sortBy: 'date',
  sortOrder: -1,
  list: {
    'featured': {
      sortBy: 'priority',
      sortOrder: -1,
    }
  }
};

Automatic group generation #

Rather than defining groups in front matter, you can define a filter function in any list object to programmatically create groups. For example, the following code creates a latest group that lists all posts dated within the past 28 days. The default reverse chronological order applies:

publican.config.js excerpt

publican.config.groupPages = {
  sortBy: 'date',
  sortOrder: -1,
  list: {
    'latest': {
      filter: data => data.date >= new Date(Date.now() - 28 * 24 * 60 * 60 * 1000)
    }
  }
};

Note that a latest filter function overrides any groups: latest set in front matter. You can prevent this by checking the data.groups Set, e.g.

publican.config.js excerpt

'latest': {
  filter: data => (
    data.groups.has('latest') ||
    data.date >= new Date(Date.now() - 28 * 24 * 60 * 60 * 1000)
  )
}

Any post with groups: latest now appears in the latest group even if it’s older than 28 days.

Generating index pages #

Publican can automatically generate paginated index pages for any group by setting a root path in the list definition, e.g. / for the home page or /latest/ for a new section of latest posts. Set the following optional values in the list or the default groupPages object:

namedescription
.sizenumber of items per paginated page
.templatethe template to use
.indexsearch engine indexing frequency, such as monthly

The following configuration creates index pages for latest posts on the home page:

publican.config.js excerpt

publican.config.groupPages = {
  sortBy: 'date',
  sortOrder: -1,
  size: 12,
  template: 'list.html',
  index: 'monthly',
  list: {
    'latest': {
      filter: data => data.date >= new Date(Date.now() - 28 * 24 * 60 * 60 * 1000),
      root: '/'
    }
  }
};

If there were 30 latest posts, an index size of 12 creates:

data.pagination #

Publican provides index pages with a data.pagination object that has the following properties.

namedescription
.pagean array of page data objects available in the current index page
.pageTotaltotal number of pages
.pageCurrentcurrent page number (zero based)
.pageCurrent1current page number (one based)
.subpageFrom1post number from
.subpageTo1post number to
.hrefBacklink to previous paginated page
.hrefNextlink to next paginated page
.hrefarray of links to all paginated pages

Showing page lists #

The following template code example lists all pages when the data.pagination object is available (it can be added to any template):

${ data?.pagination?.page?.length && `
  <nav class="pagelist">
    ${ data.pagination.page.map(p => `
      <article>
        <a href="${ p.link }">
          <h2>${ p.title }</h2>
        </a>
      </article>
    `) }
  </nav>
`}

Showing index navigation #

The following template code example shows index back and next buttons when the data.pagination object is available:

${ data?.pagination?.hrefBack && `
    <a href="${ data.pagination.hrefBack }">back</a>
`}

${ data?.pagination?.hrefNext && `
    <a href="${ data.pagination.hrefNext }">next</a>
`}

The following template code example shows a list of links to all index pages (the active page is not a link):

${ pagination?.href.forEach((page, idx) => {
  return idx == data.pagination.pageCurrent ?
    `<strong>${ idx + 1 }</strong>` :
    `<a href="${ page }">${ idx + 1 }</a>`
}) }