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

@dokall/link-preview

v1.0.11

Published

Direct link unfurling (Open Graph + platform oEmbed) without third-party preview APIs

Readme

@dokall/link-preview npm

Fetch link preview metadata (title, description, image, favicon) by unfurling URLs directly — Open Graph / Twitter cards, plus platform-specific oEmbed fallbacks. No Microlink, no Iframely, no paid preview API.

Built for Dokall Link Preview and extracted as a standalone, zero-dependency library for Node.js 18+ and Cloudflare Workers.

Features

  • Direct HTML crawl — parses og:*, twitter:*, <title>, favicon, canonical URL
  • Platform handlers — YouTube, X/Twitter, TikTok, Instagram, Threads, Reddit, Spotify, Facebook, LinkedIn
  • Bot rotation — tries multiple crawler user-agents when sites block generic fetchers
  • SSRF guard — blocks private IPs, localhost, and internal hostnames before fetching
  • Configurable — timeout, AbortSignal, headers, User-Agent, custom fetch, platform/fallback toggles
  • Zero runtime dependencies — uses native fetch only

Install

npm install @dokall/link-preview

Quick start

import { getLinkPreview, validateLinkPreview } from "@dokall/link-preview";

const result = await getLinkPreview("https://example.com");

if (result.ok && result.preview) {
  console.log(result.preview.title);
  console.log(result.preview.description);
  console.log(result.preview.image);
}

With options

const controller = new AbortController();

const result = await getLinkPreview("https://example.com", {
  timeoutMs: 8_000,
  signal: controller.signal,
  userAgent: "MyAppBot/1.0",
  headers: { "Accept-Language": "vi-VN,vi;q=0.9" },
  fallback: false, // fail instead of url-only preview
  platforms: true, // dedicated oEmbed / platform handlers (default)
});

Validate URL (SSRF-safe)

import { validateLinkPreview } from "@dokall/link-preview";

const check = validateLinkPreview("https://example.com");
if (check.ok) {
  console.log(check.url);
} else {
  console.error(check.error);
}

API

getLinkPreview(url, options?)

Returns Promise<LinkPreviewResponse>:

interface LinkPreviewResponse {
  ok: boolean;
  message?: string;
  preview?: LinkPreviewData;
}

interface LinkPreviewData {
  url: string;
  finalUrl?: string;
  title?: string;
  description?: string;
  image?: string;
  siteName?: string;
  type?: string;
  favicon?: string;
}

Options (FetchLinkPreviewOptions)

| Option | Type | Default | Description | | --- | --- | --- | --- | | timeoutMs | number | Path-dependent (8–20s) | Override request timeout for all outgoing fetches | | signal | AbortSignal | — | Cancel in-flight requests; combined with timeoutMs | | headers | Record<string, string> | — | Extra headers merged into requests. A User-Agent here also pins the UA and skips rotation | | userAgent | string | Bot rotation | Custom User-Agent; skips crawler/platform UA rotation when set (wins over headers) | | fallback | boolean | true | When false, return { ok: false } instead of a url-only preview | | platforms | boolean | true | When false, skip dedicated platform/oEmbed handlers and only crawl HTML | | fetch | typeof fetch | globalThis.fetch | Custom fetch (proxy, undici Agent, etc.) |

How fallback works: if the library cannot extract title/description/image, it normally still returns ok: true with a minimal preview (title = URL, siteName = hostname). Set fallback: false when you want a hard failure instead.

How platforms works: dedicated handlers (YouTube oEmbed, X syndication, TikTok, Instagram, Facebook, LinkedIn, …) run first when enabled. Disable them to force a plain Open Graph HTML crawl only.

validateLinkPreview(input)

Returns { ok: true, url } or { ok: false, error }.

assertSafeTargetUrl(rawUrl)

Throws if the URL is invalid or targets a blocked host (use before custom fetch logic).

Cloudflare Workers

Works in Workers — no Node-only APIs. Example route handler:

import { getLinkPreview, validateLinkPreview } from "@dokall/link-preview";

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url).searchParams.get("url");
    if (!url) return Response.json({ ok: false, message: "Missing url" }, { status: 400 });

    const validation = validateLinkPreview(url);
    if (!validation.ok) {
      return Response.json({ ok: false, message: validation.error }, { status: 400 });
    }

    const result = await getLinkPreview(validation.url, {
      timeoutMs: 10_000,
      fallback: false,
    });
    return Response.json(result);
  },
};

Custom fetch (proxy)

const result = await getLinkPreview(url, {
  fetch: (input, init) =>
    fetch(input, {
      ...init,
      // e.g. route through your Worker binding / proxy
    }),
});

Local example

git clone https://github.com/dokalldotcom/link-preview.git
cd link-preview
npm install
npm run example
# or with a custom URL:
npm run build && node examples/basic.mjs https://dokall.com

Limitations

This library unfurls URLs with plain fetch + HTML/oEmbed parsing — no headless browser, no paid proxy API. Set expectations accordingly.

IP blocking at scale

Traffic is sent directly from your server (or Worker), not through a residential proxy or paid unfurl service. At high volume, especially when repeatedly fetching the same hosts (Facebook, LinkedIn, Instagram, etc.), those sites may return 403, login walls, or CAPTCHA challenges.

Bot rotation (cycling crawler user-agents) improves success on many pages but is best-effort only — it does not bypass rate limits, IP reputation checks, or bot detection. For production at scale, add rate limiting, caching, and consider a proxy or dedicated preview API for hard targets. Pass a custom fetch if you already have a proxy layer.

No JavaScript rendering

The library does not execute JavaScript. It only reads HTML returned by the initial HTTP response and platform oEmbed endpoints.

Sites that are client-rendered SPAs without server-side Open Graph tags will often return empty or minimal metadata (blank shell HTML). Previews work best when the target page exposes og:* / twitter:* meta tags in the raw HTML, or when a platform oEmbed API is available.

Other notes

  • Facebook / Instagram / TikTok change frequently; platform handlers are maintained on a best-effort basis
  • Always validate user-supplied URLs with validateLinkPreview before fetching (SSRF protection)

Related

License

MIT © Dokall