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

linkpeek

v1.1.0

Published

Lightweight link preview and Open Graph metadata extractor for Node.js, Bun, and Deno. Extracts og tags, Twitter Cards, and JSON-LD from any URL.

Readme

linkpeek

Link preview extraction for Node.js, Bun, and Deno. One dependency.

npm bundle size CI types license

import { preview } from "linkpeek";

const result = await preview("https://www.youtube.com/watch?v=dQw4w9WgXcQ");

result.title;       // "Rick Astley - Never Gonna Give You Up"
result.image;       // "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg"
result.siteName;    // "YouTube"
result.favicon;     // "https://www.youtube.com/favicon.ico"
result.description; // "The official video for \"Never Gonna Give You Up\"..."

Install

npm install linkpeek

Also works with bun add linkpeek and import { preview } from "npm:linkpeek" (Deno).

Why linkpeek

Most link preview libraries depend on Cheerio, build a full DOM, download the entire page, and only run on Node. linkpeek takes a different approach:

  • 1 dependency (htmlparser2) — not 4, not a plugin tree
  • Stops at </head> — streams 30 KB, not the full 2 MB page
  • SAX streaming — no DOM construction, ~2 ms parse time
  • SSRF protection — private/internal IPs blocked by default
  • Runs everywhere — Node.js 20+, Bun, Deno, and edge runtimes (tested in CI)

Note: linkpeek should be used server-side only. Use it in an API route and return the result to the client.

Presets

import { preview, presets } from "linkpeek";

// Default: fast (30 KB limit, head only, no meta-refresh)
const result = await preview(url);

// Quality: body JSON-LD + image fallback + meta-refresh
const result = await preview(url, presets.quality);

// Custom: spread a preset and override
const result = await preview(url, { ...presets.quality, timeout: 3000 });

| Preset | What it enables | | ----------------- | ------------------------------------------- | | presets.fast | Default behavior — explicit version of {} | | presets.quality | Body JSON-LD, image fallback, meta-refresh |

Error handling

preview() throws for invalid input and blocked URLs:

try {
  const result = await preview(url);
} catch (err) {
  // "Invalid URL"
  // "Only http and https URLs are supported"
  // "URLs pointing to private/internal networks are not allowed"
  console.error(err.message);
}

API

preview(url, options?)

Fetches a URL and extracts link preview metadata. Returns Promise<PreviewResult>.

Options

| Option | Type | Default | Description | | -------------------- | ------------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | timeout | number | 8000 | Request timeout in milliseconds | | maxBytes | number | 30_000 | Max bytes to stream | | userAgent | string | "Twitterbot/1.0" | User-Agent sent with requests. Twitterbot gets pre-rendered HTML from most platforms | | followRedirects | boolean | true | Follow HTTP 3xx redirects | | headers | Record<string, string> | {} | Extra request headers (e.g. cookies, auth tokens) | | allowPrivateIPs | boolean | false | Allow fetching private/internal IPs. Keep false in production to prevent SSRF attacks | | followMetaRefresh | boolean | false | Follow <meta http-equiv="refresh"> redirects when no title is found. Enable to handle Cloudflare-challenged pages at the cost of an extra HTTP round-trip | | includeBodyContent | boolean | false | Continue scanning <body> for JSON-LD scripts and <img> fallbacks after </head>. Enable together with a higher maxBytes for best quality |

Result Fields

| Field | Type | Description | | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | url | string | Final resolved URL | | statusCode | number | HTTP status code (200, 301, 404, etc.). Returns 0 when using parseHTML() directly | | title | string \| null | Page title (og:titletwitter:title → JSON-LD → <title>) | | description | string \| null | Description (og:descriptiontwitter:descriptionmeta[name=description] → JSON-LD) | | image | string \| null | Preview image (og:imagetwitter:image → JSON-LD → itemprop=image → first <img>) | | imageAlt | string \| null | Image alt text (og:image:alttwitter:image:alt) | | imageWidth | number \| null | Image width from og:image:width | | imageHeight | number \| null | Image height from og:image:height | | siteName | string | Site name (og:site_name → JSON-LD publisher → hostname fallback) | | favicon | string \| null | Favicon URL (largest apple-touch-iconlink[rel=icon]/favicon.ico) | | mediaType | string | Content type from og:type, defaults to "website" | | canonicalUrl | string | Canonical URL (link[rel=canonical]og:url → request URL) | | author | string \| null | Author name (JSON-LD author → meta[name=author] → Dublin Core) | | locale | string \| null | Locale from og:locale | | lang | string \| null | Language code (<html lang><meta http-equiv="content-language">og:locale prefix) | | publishedDate | string \| null | Published date (article:published_time → JSON-LD datePublished → Dublin Core) | | keywords | string[] \| null | Keywords from meta[name=keywords] | | video | string \| null | Video URL from og:video | | audio | string \| null | Audio URL from og:audio | | twitterCard | string \| null | Twitter card type (summary, player, summary_large_image) | | twitterSite | string \| null | Twitter @handle from twitter:site | | twitterCreator | string \| null | Author's Twitter @handle from twitter:creator | | themeColor | string \| null | Theme color from meta[name=theme-color] | | oEmbedUrl | string \| null | Discovered oEmbed endpoint URL from <link rel="alternate" type="application/json+oembed">. Not fetched — returned for the caller to resolve if needed |

parseHTML(html, baseUrl, options?)

Parses an HTML string directly. Use this when you already have the HTML.

import { parseHTML } from "linkpeek";

const result = parseHTML(
  "<html><head><title>Hello</title></head></html>",
  "https://example.com",
);
console.log(result.title); // "Hello"

Parameters:

  • html (string) — The HTML content to parse
  • baseUrl (string) — Base URL for resolving relative URLs
  • options? ({ includeBodyContent?: boolean }) — Pass { includeBodyContent: true } to scan <body> for JSON-LD and image fallbacks

Returns PreviewResult.

Examples

Framework examples for Next.js, Express, Cloudflare Workers, React, Supabase, and Bun.