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

astro-loader-glob-frontmatter

v0.2.1

Published

Astro content loader that injects frontmatter from external YAML/JSON files

Readme

astro-loader-glob-frontmatter

An Astro content loader that wraps glob() and injects frontmatter from external YAML/JSON files—so you can manage metadata for dozens (or hundreds) of content files without touching every single one.

The Problem

Astro content collections require frontmatter in every markdown file. For a documentation site with 200 pages, that means 200 files with sidebar.order, title, description, and whatever else your schema demands. Maintaining that across the whole tree is tedious, hard to review in aggregate, and pollutes your content files with framework config.

The Fix

Keep your frontmatter in dedicated files—one centralized file, per-directory files sitting next to the content, or both. This loader picks them up, deep-merges them with any in-file frontmatter, and hands the result to Astro's parseData for schema validation. Your markdown files stay clean.

Install

pnpm add astro-loader-glob-frontmatter

Requires astro >= 5.0.0 as a peer dependency.

Quick Start

// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { globFrontmatter } from 'astro-loader-glob-frontmatter'

const docs = defineCollection({
  loader: globFrontmatter({
    pattern: '**/*.md',
    base: './docs',
    frontmatter: './frontmatter.yml',
  }),
  schema: z.object({
    title: z.string(),
    sidebar: z.object({
      order: z.number(),
      label: z.string().optional(),
    }).optional(),
  }),
})

export const collections = { docs }

That's it. The loader wraps Astro's built-in glob(), so all the standard glob behavior—file watching, caching, digest computation—works the same.

Options

globFrontmatter extends Astro's glob() options with one additional property:

| Option | Type | Default | Description | |--------|------|---------|-------------| | frontmatter | string | no value | Path to a centralized frontmatter YAML/JSON file, relative to the Astro root |

All other options (pattern, base, generateId, etc.) are passed through to glob() as-is.

Frontmatter File Formats

Centralized file

Set the frontmatter option to point at a single YAML or JSON file. Two key formats are supported—nested and flat—and you can mix them in the same file.

Nested mirrors your directory structure:

guides:
  installation.md:
    title: Installation
    sidebar:
      order: 1
  getting-started.md:
    title: Getting Started
    sidebar:
      order: 2
components:
  accordion.md:
    title: Accordion
    description: Expandable content sections

Flat uses path keys directly:

guides/installation.md:
  title: Installation
  sidebar:
    order: 1
guides/getting-started.md:
  title: Getting Started

The detection rule is simple: if a key ends in .md, .mdx, or .mdoc, it's a content entry. Otherwise it's a directory name and the loader recurses into it.

Per-directory files

Drop a frontmatter.yml, frontmatter.yaml, or frontmatter.json in any directory under your base path and the loader picks it up automatically. Keys are filenames relative to that directory:

# docs/components/frontmatter.yml
README.md:
  title: Components Overview
  slug: components
accordion.md:
  title: Accordion
  sidebar:
    order: 3

No config flag needed. If the file exists, it's used. If you don't want per-directory frontmatter, don't create the files.

Merge Cascade

Four layers, from broadest to most specific:

centralized file  →  per-directory file  →  H1 title  →  in-file frontmatter
   (broadest)        (directory-scoped)     (from body)    (most specific)

In-file frontmatter always wins. If a key exists in the markdown file's frontmatter, that value is used. The H1 title sits between external frontmatter and in-file frontmatter—it fills in the title field when external files don't provide one, but in-file title still takes precedence. Per-directory files fill in gaps and override the centralized file. The centralized file provides defaults for everything else.

For nested objects, merging is deep. If your centralized file sets sidebar.badge: new and the per-directory file sets sidebar.order: 2, the result is sidebar: { order: 2, badge: new }—not a wholesale replacement.

Example

Given this centralized file:

components:
  accordion.md:
    title: Central Title
    description: From central
    sidebar:
      order: 10
      badge: new

This per-directory file:

# docs/components/frontmatter.yml
accordion.md:
  title: Per-Dir Title
  sidebar:
    order: 2

And this markdown frontmatter:

---
title: My Accordion
draft: true
---

The merged result is:

title: My Accordion          # in-file wins
description: From central     # only in central, inherited
draft: true                   # only in file, preserved
sidebar:
  order: 2                    # per-dir wins over central
  badge: new                  # central, inherited via deep merge

Title from H1

The loader automatically extracts the first # Heading from your markdown content and uses it as the title field. This means you can write natural markdown that looks good on GitHub:

---
sidebar:
  order: 3
---

# Accordion

The accordion component expands and collapses content.

The # Accordion heading becomes title: "Accordion" in your frontmatter data, and is stripped from the rendered body to prevent duplication.

Rules:

  • The H1 must be the first non-blank content after frontmatter
  • If frontmatter already has a title field, the in-file title wins (H1 is still stripped from the body)
  • Inline markdown in the heading (# My **Bold** Title) is flattened to plain text
  • Only the first H1 is extracted — subsequent H1s are left in the body

Dev Mode

The loader automatically watches all frontmatter files for changes during astro dev. Edit a frontmatter.yml and the collection reloads—no restart needed.

How It Works

The loader intercepts glob()'s parseData call. When glob processes each content file, it calls parseData with the file's frontmatter. This loader wraps that call, looks up the file's path in a pre-built frontmatter map, deep-merges the external data underneath the file's own frontmatter, then delegates to the real parseData for schema validation.

One pass, no re-processing, and glob's built-in caching works normally.

License

MIT