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

head-metadata

v0.1.0

Published

Typed HTML head metadata extraction with configurable single and collection extractors

Readme

head-metadata extracts typed values from the first <head> element in an HTML document. Choose an output field and extractor for each value you need. Single extractors keep one value, while collection extractors preserve repeated values in document order.

  • Extract titles, base URLs, meta tags, and links without building a full-page DOM
  • Preserve repeated Open Graph images, icons, alternates, and other metadata
  • Infer result fields and value types from the extractor config
  • Decode character references in attributes and ordinary text
  • Add custom extractors for project-specific head elements
import { extractHeadMetadata, linkExtractor, metaExtractor, titleExtractor } from 'head-metadata';

const metadata = extractHeadMetadata(
  `<head>
    <title>Example</title>
    <meta property="og:image" content="/first.png">
    <meta property="og:image" content="/second.png">
    <link rel="icon" sizes="48x48" href="/favicon.png">
  </head>`,
  {
    title: titleExtractor,
    meta: metaExtractor,
    links: linkExtractor
  }
);

metadata.title; // 'Example' | undefined
metadata.meta; // both og:image entries, in document order
metadata.links; // [{ rel: ['icon'], sizes: ['48x48'], href: '/favicon.png' }]

Migrating from 0.0.x? See MIGRATION.md.

Install

npm install head-metadata

Usage

Pass the HTML string and an extractor config to extractHeadMetadata():

import {
  baseExtractor,
  extractHeadMetadata,
  linkExtractor,
  metaExtractor,
  titleExtractor
} from 'head-metadata';

const metadata = extractHeadMetadata(html, {
  title: titleExtractor,
  baseHref: baseExtractor,
  meta: metaExtractor,
  links: linkExtractor
});

Config keys name the output fields. Each extractor's tag selects the HTML element it receives. Multiple extractors can select the same tag and produce different fields.

The extractor kind determines whether an output field is optional or always present:

| Extractor kind | Result | | -------------- | ---------------------------------------------------------------------- | | single | First non-null result. The field is omitted when nothing produces one. | | collection | All non-null results in document order. An empty array when unmatched. |

API

extractHeadMetadata(html, extractors)

Extracts configured values from the first <head> element.

const metadata = extractHeadMetadata(html, {
  pageTitle: titleExtractor,
  meta: metaExtractor
});

metadata.pageTitle; // string | undefined
metadata.meta; // TMetaMetadata[]

The function stops parsing after the first selected head. Tokenizer errors and extractor callback errors propagate to the caller.

Extractor callbacks receive a TXmlNode:

| Field | Description | | ------------ | ------------------------------------------- | | local | Lowercase local element name | | prefix | Lowercase namespace prefix, when present | | attributes | Attributes with decoded values | | content | Child nodes and non-whitespace text content |

Character references are decoded in ordinary text and attribute values. Script, style, and CDATA content remain literal. URLs are not resolved or otherwise changed.

Built-In Extractors

titleExtractor

Selects <title> and returns its trimmed text. Configure it as a single field:

const metadata = extractHeadMetadata(html, {
  title: titleExtractor
});

The first title that produces text wins. metadata.title is undefined when no title produces text.

baseExtractor

Selects <base> and returns its href without resolving it:

const metadata = extractHeadMetadata(html, {
  baseHref: baseExtractor
});

The first base element with an href wins.

metaExtractor

Selects <meta> elements and returns TMetaMetadata[]. Each entry preserves the supported attributes that are present:

const metadata = extractHeadMetadata(
  `<head>
    <meta name="description" content="An example page">
    <meta property="og:image" content="/first.png">
    <meta property="og:image" content="/second.png">
  </head>`,
  { meta: metaExtractor }
);

metadata.meta;
// [
//   { name: 'description', content: 'An example page' },
//   { property: 'og:image', content: '/first.png' },
//   { property: 'og:image', content: '/second.png' }
// ]

Supported fields are charset, name, property, httpEquiv, and content. An element without any supported attribute is skipped.

linkExtractor

Selects <link> elements that contain both rel and href, and returns TLinkMetadata[]:

const metadata = extractHeadMetadata(
  `<head>
    <link rel="icon" type="image/png" sizes="32x32 48x48" href="/favicon.png">
  </head>`,
  { links: linkExtractor }
);

metadata.links;
// [{
//   rel: ['icon'],
//   href: '/favicon.png',
//   type: 'image/png',
//   sizes: ['32x32', '48x48']
// }]

rel and sizes are split on ASCII whitespace. Relation tokens are lowercase. The extractor also preserves media and hreflang when present.

Custom Extractors

Use TSingleExtractor<GValue> for the first matching value and TCollectionExtractor<GValue> for every matching value. Return null to skip an element.

import { extractHeadMetadata, type TSingleExtractor } from 'head-metadata';

const descriptionExtractor = {
  tag: 'meta',
  type: 'single',
  callback: (node) => {
    const name = node.attributes.find((attribute) => attribute.local === 'name')?.value;
    const content = node.attributes.find((attribute) => attribute.local === 'content')?.value;
    return name === 'description' ? (content ?? null) : null;
  }
} satisfies TSingleExtractor<string>;

const metadata = extractHeadMetadata(html, {
  description: descriptionExtractor
});

metadata.description; // string | undefined

Set tag to the lowercase HTML tag name. Config keys remain independent from tag names, so several extractors can read the same element:

const metadata = extractHeadMetadata(html, {
  meta: metaExtractor,
  description: descriptionExtractor
});

Scope

head-metadata parses HTML you already have. The calling application remains responsible for:

  • Fetching pages and following redirects
  • Limiting response size and request duration
  • Resolving relative URLs against the page URL and <base href>
  • Selecting preferred Open Graph, Twitter, and fallback values
  • Validating or downloading referenced resources

FAQ

Why use extractors instead of returning every head element?

Extractors keep parsing separate from application policy. They also keep the result small and typed: callers choose the elements and output fields they need.

Are repeated metadata values preserved?

Yes. Collection extractors append every non-null result in document order. This allows callers to choose among repeated Open Graph images, icons, and alternate links.

Does it parse the entire document?

No. It processes only the first <head> selected by xml-tokenizer, then advances the tokenizer to the end of its input. The HTML string is still supplied by the caller; network streaming and response limits are outside this package.