metalsmith-simple-pagination

July 21, 2026 · View on GitHub

A simple pagination plugin for Metalsmith that creates paginated directories from a source directory of similar files, with pagination metadata compatible with metalsmith-pagination.

metalsmith:plugin npm: version license: MIT coverage ESM

This package is ESM-only and requires Node >= 22. CommonJS projects on Node 22 can still consume it with require('metalsmith-simple-pagination') thanks to Node's require(esm) support.

Features

  • Creates paginated directories from a source directory with a configurable number of files per page
  • Generates clean pagination URLs for both permalink and non-permalink styles
  • Provides pagination metadata compatible with metalsmith-pagination
  • Customizable sorting by any metadata property, including nested properties
  • Preserves all file metadata while adding pagination references
  • Works seamlessly with blog posts and similar collections of files

Installation

npm install metalsmith-simple-pagination

Usage

This plugin follows the standard Metalsmith plugin pattern.

import Metalsmith from 'metalsmith';
import simplePagination from 'metalsmith-simple-pagination';

Metalsmith(import.meta.dirname)
  .use(
    simplePagination({
      directory: 'blog', // Directory containing files to paginate
      perPage: 10, // Number of posts per page
      sortBy: 'date', // Sort posts by this property (e.g., date or post.date)
      reverse: true, // Sort in descending order (newest first)
      firstIndexFile: 'blog.md', // First page will update metadata of this file
    })
  )
  .build((err) => {
    if (err) throw err;
    console.log('Build complete!');
  });

Options

OptionTypeDefaultDescription
directoryString'blog'Directory containing files to paginate
perPageNumber10Number of files per page
sortByString'date'Property to sort by (e.g., 'date' or 'post.date')
reverseBooleantrueWhether to reverse the sort order (true = newest first)
outputDirString':directory/:num'Directory pattern for output (supports :directory and :num)
indexLayoutString'blog-index.njk'Layout to use for index pages
firstIndexFileString'blog.md'Name of first page file (e.g., 'blog.md')
usePermalinksBooleantrueWhether to use permalinks-style URLs (/blog/ vs /blog.html)

Examples

Basic Usage

import Metalsmith from 'metalsmith';
import simplePagination from 'metalsmith-simple-pagination';

Metalsmith(import.meta.dirname)
  .source('./src')
  .destination('./build')
  .use(simplePagination())
  .build((err) => {
    if (err) throw err;
    console.log('Build complete!');
  });

With Options

import Metalsmith from 'metalsmith';
import simplePagination from 'metalsmith-simple-pagination';

Metalsmith(import.meta.dirname)
  .source('./src')
  .destination('./build')
  .use(
    simplePagination({
      directory: 'articles',
      perPage: 5,
      sortBy: 'publishDate',
      reverse: true,
      outputDir: ':directory/page/:num',
      indexLayout: 'article-list.njk',
      firstIndexFile: 'articles.md',
    })
  )
  .build((err) => {
    if (err) throw err;
    console.log('Articles paginated!');
  });

Advanced Usage with Nested Property Sorting

import Metalsmith from 'metalsmith';
import simplePagination from 'metalsmith-simple-pagination';

Metalsmith(import.meta.dirname)
  .source('./src')
  .destination('./build')
  .use(
    simplePagination({
      directory: 'posts',
      perPage: 8,
      sortBy: 'post.metadata.publishedAt', // Sort by nested property
      reverse: true,
      outputDir: 'blog/page/:num',
      usePermalinks: false,
      firstIndexFile: 'index.md',
    })
  )
  .build((err) => {
    if (err) throw err;
    console.log('Posts paginated with nested sorting!');
  });

How It Works

This plugin provides a simple way to paginate a collection of files (like blog posts) into separate pages. Here's what it does:

  1. Sources files: Finds all files in the specified directory
  2. Sorts files: Sorts them by the specified property (e.g., date)
  3. Groups files: Creates groups based on the perPage setting
  4. Creates pages: For each group:
    • Creates a new directory following the outputDir pattern
    • Moves the files to this directory
    • Adds pagination metadata
    • Creates an index.html file with the pagination information

First Page Handling

For the first page, the plugin:

  1. Moves the first perPage files to the root of your directory (e.g., /blog/)
  2. Updates the specified firstIndexFile (e.g., blog.md) with pagination metadata

Generated Metadata

Each paginated file will have these additional properties:

  • originalPath: The original file path before pagination
  • pageNumber: The page number this file appears on
  • totalPages: Total number of pages in the pagination

Each page's index file will have this pagination metadata:

{
  pagination: {
    name: 'blog',           // The directory name
    num: 2,                 // Current page number
    pages: 5,               // Total number of pages
    files: [...],           // Array of file metadata for this page
    next: '/blog/3',        // URL to next page (null if last page)
    previous: '/blog',      // URL to previous page (null if first page)
    first: '/blog',         // URL to first page
    last: '/blog/5'         // URL to last page
  },
  pageFiles: [...],         // Same as pagination.files (for backward compatibility)
  date: [Date object],      // Most recent date from files on this page
  layout: 'blog-index.njk', // Layout template for the index page
}

Example Project Structure

Before pagination:

src/
├── blog/
│   ├── etiam-porta-sem-malesuada-magna-mollis-euismod.md
│   ├── curabitur-blandit-empus-porttitor.md
│   ├── cras-mattis-consectetur-purus.md
│   ├── ipsum-cras-ullamcorper-fringilla.md
│   └── duis-mollis-est-non-commodo-uctus.md
└── blog.md  // Index page for the blog

After pagination (with perPage = 2):

build/
├── blog/
│   ├── index.md        // First page with pagination metadata
│   ├── 2/
│   │   └── index.html  // Pagination metadata for page 2
│   ├── 3/
│   │   └── index.html  // Pagination metadata for page 3
│   ├── etiam-porta-sem-malesuada-magna-mollis-euismod/
│   │   └── index.md    // Individual post
│   ├── curabitur-blandit-empus-porttitor/
│   │   └── index.md    // Individual post
│   ├── cras-mattis-consectetur-purus/
│   │   └── index.md    // Individual post
│   ├── ipsum-cras-ullamcorper-fringilla/
│   │   └── index.md    // Individual post
│   └── duis-mollis-est-non-commodo-uctus/
│       └── index.md    // Individual post

At this point the generated index files (2/index.html and 3/index.html)will be empty but their metadata will be populated.

Test Coverage

This plugin maintains high test coverage to ensure reliability. Current test coverage is displayed in the badge at the top of this README.

To run tests locally:

npm test

To view coverage details:

npm run coverage

Debug

This plugin uses Metalsmith's debug functionality. To enable debug logs, set the DEBUG environment variable:

metalsmith.env('DEBUG', 'metalsmith-simple-pagination')

CLI Usage

To use this plugin with the Metalsmith CLI, add it to your metalsmith.json file:

{
  "plugins": {
    "metalsmith-simple-pagination": {
      "directory": "blog",
      "perPage": 10,
      "sortBy": "date",
      "reverse": true,
      "firstIndexFile": "blog.md"
    }
  }
}

License

MIT