Metalsmith Sectioned Blog Pagination

July 21, 2026 ยท View on GitHub

Metalsmith plugin that generates metadata for blog pagination for pages built with a modular page building paradigm.

metalsmith: plugin npm: version license: MIT coverage ESM

Features

  • Generates pagination metadata for blog landing pages built with a modular/sectioned page paradigm
  • Fills in pagingParams on a section marked hasPagingParams: true
  • Optionally counts posts from a named @metalsmith/collections collection
  • ESM-only, requires Node >= 22. CommonJS consumers can load it on Node 22 via the built-in ESM interop.

Requirements

  • Node.js >= 22.0.0
  • Metalsmith >= 2.6.0

Installation

npm install metalsmith-sectioned-blog-pagination

Usage

Pass options to metalsmith-sectioned-blog-pagination in metalsmith.use :

The plugin must be used before the Markdown, Permalinks and Layouts plugins.

Metalsmith(import.meta.dirname)
  .use(collections({
    blog: {
      pattern: "blog/*.md",
      sortBy: "date",
      reverse: true,
      limit: 50,
    },
  }))
  .use(blogPages({
    "pagesPerPage": 12,            // Number of blog posts per page
    "blogDirectory": "blog/",      // Directory containing your blog posts
    "mainTemplate": "blog.md"      // Main blog template file (default: "blog.md")
  }))
  .use(markdown())
  .use(permalinks())
  .use(layouts())
  ...

Options

OptionTypeDefaultDescription
pagesPerPagenumber6Number of blog posts to display per page
blogDirectorystring'blog/'Directory containing blog post files (with trailing slash)
mainTemplatestring'blog.md'Main blog template file to use as template for pagination
collectionNamestringnoneName of a @metalsmith/collections collection to count posts from, instead of scanning blogDirectory. Use this when the directory holds files that are not collection members (category landing pages, generated pages), which would otherwise inflate the page count. Requires the collections plugin to run first.

Section frontmatter

The main template must contain a section marked hasPagingParams: true. That is all the frontmatter needs; the plugin creates a pagingParams object on that section and fills it in at build time:

sections:
  - sectionType: blog-list
    hasPagingParams: true

After the build the section carries:

pagingParams:
  numberOfBlogs: 42 # Total posts in the collection
  numberOfPages: 7 # Total pagination pages
  pageLength: 6 # Posts per page (pagesPerPage)
  pageStart: 0 # Index of this page's first post
  pageNumber: 1 # This page's number, 1-indexed

Declaring placeholder keys in the frontmatter still works: any of the five keys that already exist anywhere in the section are updated in place, and only the missing ones are added under pagingParams. (Before v1.4.0 the placeholders were required โ€” a section with only hasPagingParams: true was silently left without paging values.)

Examples

Basic Blog Pagination

Create paginated blog pages with 10 posts per page:

metalsmith
  .use(
    collections({
      blog: {
        pattern: 'blog/*.md',
        sortBy: 'date',
        reverse: true,
      },
    })
  )
  .use(
    blogPages({
      pagesPerPage: 10,
      blogDirectory: 'blog/',
      mainTemplate: 'blog.md',
    })
  );

Multiple Blog Sections

For sites with multiple blog sections, run the plugin multiple times:

metalsmith
  // Tech blog section
  .use(
    collections({
      techBlog: {
        pattern: 'tech/*.md',
        sortBy: 'date',
        reverse: true,
      },
    })
  )
  .use(
    blogPages({
      pagesPerPage: 8,
      blogDirectory: 'tech/',
      mainTemplate: 'tech-blog.md',
    })
  )
  // Personal blog section
  .use(
    collections({
      personalBlog: {
        pattern: 'personal/*.md',
        sortBy: 'date',
        reverse: true,
      },
    })
  )
  .use(
    blogPages({
      pagesPerPage: 5,
      blogDirectory: 'personal/',
      mainTemplate: 'personal-blog.md',
    })
  );

Custom Blog Directory Structure

Use a nested directory structure for your blog:

metalsmith.use(
  blogPages({
    pagesPerPage: 15,
    blogDirectory: 'content/articles/',
    mainTemplate: 'articles.md',
  })
);
// This will create: /content/articles/, /content/articles/2/, etc.

During the build process, the plugin will create a set of blog landing pages with the specified number of blog posts per page, e.g. /blog/, /blog/2, /blog/3... In a Nunjucks template, a pager would be constructed like this:

<ul class="blogs-pagination">
  {% for i in range(0, params.numberOfPages) -%}
  <li {% if ((i + 1)="" ="params.pageNumber)" %}class="active" {% endif %}>
    {% if i == 0 %}
    <a href="/blog/">1</a>
    {% else %}
    <a href="/blog/{{ i + 1 }}/">{{ i + 1 }}</a>
    {% endif %}
  </li>
  {%- endfor %}
</ul>

And complete template implementation in Nunjucks for such a blog landing page can be viewed here. And here is an example of an implementation.

Debug

To enable debug logs, set the DEBUG environment variable to metalsmith-sectioned-blog-pagination:

Linux/Mac:

DEBUG=metalsmith-sectioned-blog-pagination

Windows:

set DEBUG=metalsmith-sectioned-blog-pagination

CLI usage

To use this plugin with the Metalsmith CLI, add metalsmith-sectioned-blog-pagination to the plugins key in your metalsmith.json file:

{
  "plugins": [
    {
      "metalsmith-sectioned-blog-pagination": {
        "pagesPerPage": 12,
        "blogDirectory": "blog/"
      }
    }
  ]
}

Test Coverage

This project maintains high statement and line coverage for the source code. Coverage is measured with Node's native test runner (node --test --experimental-test-coverage).

Author

werner@glinka.co

License

MIT