npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@raredigits/11ty-rare-related-posts

v0.1.0

Published

Eleventy plugin: related-posts filter with curated picks and shared-label auto-fill. Works in Liquid and Nunjucks, configurable to any collection/field.

Readme

@raredigits/11ty-rare-related-posts

A small Eleventy plugin that adds a relatedPosts filter. For each page it returns:

  1. Curated picks — whatever the page lists in related: [...], in your order, or
  2. Auto-fill — when nothing is curated, the posts that share the most labels/tags with the current page (ranked by overlap, then recency).

Works in Liquid and Nunjucks, reads its collection itself, and every name (collection, fields, the filter itself) is configurable — so it drops into any site without clashing with what's already there.

📦 Source & issues: github.com/raredigits/11ty

Install

npm install @raredigits/11ty-rare-related-posts

Setup

// eleventy.config.js
import relatedPosts from "@raredigits/11ty-rare-related-posts";

export default function (eleventyConfig) {
  eleventyConfig.addPlugin(relatedPosts, {
    collection: "posts",     // collection to auto-fill from (null = all pages)
    labelsField: "tags",     // front-matter field holding topics
    relatedField: "related", // front-matter field holding curated picks
    excludeField: "relation",// front-matter field to opt a post out
    filterName: "relatedPosts",
    limit: 3,                // how many related posts to show
    fields: {                // what each result card exposes
      title: true,
      image: true,
      tldr: true,
      tags: false,
      date: true,
    },
    dateFormat: "YYYY",      // token string, Intl options, or a function
    locale: "en",
  });
}

All options are optional. With zero config the plugin auto-fills from every page (collections.all) using the tags field — so it works on a default Eleventy setup with no custom collections.

Options

| Option | Default | Purpose | | -------------- | ---------------- | -------------------------------------------------------------- | | collection | null | Collection name to auto-fill from. null → all pages. | | labelsField | "tags" | Front-matter field used to score topical overlap. | | relatedField | "related" | Front-matter field holding a curated list. | | excludeField | "relation" | Front-matter field that opts a post out (see below). | | filterName | "relatedPosts" | Name of the filter in templates (rename to avoid clashes). | | limit | 3 | Max number of auto-filled posts. | | fields | (see below) | Which fields each result card exposes, and where they map. | | dateFormat | "YYYY" | How dateFormatted is rendered. | | locale | "en" | Locale for month names / Intl formatting. | | warn | true | Warn in the build log when a curated slug can't be resolved. |

If collection names a collection that's empty or missing, the plugin falls back to all pages instead of erroring.

Excluding a post — relation: off

Add the exclude field to any page's front matter to keep it out of auto-fill everywhere (e.g. a changelog, a legal page, a stub):

---
title: Changelog
relation: off      # also accepts false / no / none
---

Excluded posts are skipped only by auto-fill — you can still link them explicitly via related when you really mean to. Rename the field with the excludeField option.

Choosing what each card shows — fields

Each result is a normalized card so your template doesn't depend on front-matter field names. Per field, the value can be:

  • false — omit it,
  • true — include it, reading the conventional source field,
  • "someField" — include it, reading from someField instead, or
  • ["a", "b"] — try each field in order, first non-empty value wins.
fields: {
  title: true,             // → card.title  (from `title`)
  image: ["cover", "img"], // → card.image  (try `cover`, fall back to `img`)
  tldr: true,              // → card.tldr   (from `tldr`)
  tags: true,              // → card.tags[] (defaults to your labelsField)
  date: true,              // → card.dateFormatted
}

The array form is useful when front matter is inconsistent — e.g. some posts store the image under cover and others under img.

A card always carries url, date (raw), and data (the raw front matter) as an escape hatch, plus whichever of title / image / tldr / tags / dateFormatted you enabled.

Date format — dateFormat

dateFormat: "YYYY"             // 2024
dateFormat: "DD MMM YYYY"      // 09 Mar 2024   (tokens: YYYY YY MMMM MMM MM M DD D)
dateFormat: { dateStyle: "medium" }   // Intl.DateTimeFormat options, uses `locale`
dateFormat: (d) => `${d.getFullYear()}` // a function for full control

Use in templates

Styling: the Rare Styles library already ships a class set for this module (the related / feature-row markup below), so if you use it you don't need to write any CSS — just drop in the markup.

Auto mode — the plugin finds the collection for you:

{% assign related = page.url | relatedPosts %}
{% assign related = page.url | relatedPosts: 5 %}   {# override the limit #}

{% if related.size > 0 %}
  {% for post in related %}
    {% if post.image %}<img src="{{ post.image }}" alt="{{ post.title }}">{% endif %}
    <h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
    {% if post.tldr %}<p>{{ post.tldr }}</p>{% endif %}
    <span>{{ post.dateFormatted }}</span>
  {% endfor %}
{% endif %}

Use the card fields (post.title, post.image, post.tldr, post.tags, post.dateFormatted) so the plugin's fields and dateFormat settings drive the output. Reach into post.data for anything you didn't expose as a field.

Explicit mode — pass a collection in yourself (handy if you don't want the plugin to pick the collection, or have several):

{% assign related = collections.news | relatedPosts: page.url %}
{% assign related = collections.news | relatedPosts: page.url, 5 %}

The same works in Nunjucks ({% set related = page.url | relatedPosts %}).

Curating related posts by hand

Add a related list to a page's front matter. Curation always wins over auto-fill, keeps your order, and ignores limit:

---
title: My post
related:
  - my-other-post        # slug, url, or last path segment — all resolve
  - /blog/deep-dive/
---

Curated slugs resolve against every page on the site, not just the configured collection — so you can link anywhere.

Hiding a post from recommendations

To stop a specific post from ever being auto-suggested anywhere on the site, add relation: off to that post's front matter:

---
title: "Vacation Notice: The August Disappearing Act"
relation: off      # also accepts false / no / none
---

That's it — rebuild and it disappears from every other post's related list. It's skipped only by auto-fill, so you can still link to it deliberately with related. The field name is configurable via the excludeField option.

No collection at all?

If a site has nothing to resolve against, list related items inline as objects:

related:
  - { url: "https://example.com/x", title: "An external read" }
  - { url: "/standalone/", title: "A standalone page", img: "/img/x.png" }

These render with the same post.url / post.data.title shape as real collection items.

Tuning the algorithm

The recommendation logic lives in src/related.js as a pure function with no Eleventy dependency, and is exported separately:

import { pickRelated } from "@raredigits/11ty-rare-related-posts/logic";

It's covered by unit tests (npm test) that run without a build, so you can iterate on scoring quickly.

Local development

Eleventy loads plugins once at startup and does not watch node_modules. So when you edit this package while developing against a site (via npm link or a file: dependency), the running dev server keeps the old version in memory — your changes won't show up, and you may see stale or empty output.

After changing anything under src/, restart the dev server:

# Ctrl+C to stop, then:
npm start

Edits to the site's own templates and content hot-reload as usual; only changes to this package need a restart. Running npm test here needs no restart — the tests import the source directly.

License

MIT