@cutticat/favicon-fetch
Favicon discovery and metadata library.
Table of Contents
- Overview
- Installation
- CLI
- Quick Start
- API
- Scan Options
- Probe Diagnostics
- Supported URLs
- Documentation
- Requirements
Overview
@cutticat/favicon-fetch finds favicon candidates on a page and enriches them with metadata:
<link rel="icon">,apple-touch-icon,mask-icon, and others- icons from Web App Manifest (
<link rel="manifest">) - well-known paths on the origin (
/favicon.ico,/favicon.png, …)
The library does not download icon bytes through the API, does not perform SSRF checks, and does not cache — that is the caller’s responsibility (for example, the CuttiCat backend). Use the CLI for downloading and debugging.
After scanning, use pickFavicon to choose the best candidate from the list.
HTTP probe errors are not thrown — they are passed to the onProbeFailure callback. To abort the scan, throw from the callback.
Installation
pnpm i @cutticat/favicon-fetch
CLI
The package ships the favicon-fetch command (bin field). In the library repository, use the favicon:fetch script (fetch is taken by pnpm’s built-in command).
# In the @cutticat/favicon-fetch repository (after pnpm run build)
pnpm favicon:fetch --list https://github.com
pnpm favicon:fetch https://github.com
pnpm favicon:fetch https://github.com ./favicon.svg
# In a project with @cutticat/favicon-fetch as a dependency
favicon-fetch --list https://github.com
favicon-fetch https://github.com ./icon.png
| Mode | Behavior |
|---|---|
--list |
JSON array of FaviconMetadata[] (all discovered candidates) |
| no flags | pickFavicon → download the best icon; JSON { output, href, bytes } |
The second argument is the output file path. By default, the filename from the icon URL is used (for example, favicon.ico, favicon.svg).
Quick Start
import { pickFavicon, scanFaviconsFromUrl } from "@cutticat/favicon-fetch"
const favicons = await scanFaviconsFromUrl("https://example.com", {
// HTML + manifest discovery only, no synthetic paths or HEAD probe
scanPaths: [],
probe: [],
mimeTypeFetchPolicy: "never",
sizesFetchPolicy: "never",
})
const best = pickFavicon(favicons)
console.log(best?.href) // https://example.com/favicon.png
Full page URL scan (HTML + manifest + synthetic paths + HEAD probe for synthetic):
import { pickFavicon, scanFaviconsFromUrl } from "@cutticat/favicon-fetch"
const favicons = await scanFaviconsFromUrl("https://example.com")
const best = pickFavicon(favicons)
Scanning already-fetched HTML:
import { pickFavicon, scanFaviconsFromHtml } from "@cutticat/favicon-fetch"
const favicons = await scanFaviconsFromHtml(html, {
pageUrl: "https://example.com/page",
})
const best = pickFavicon(favicons)
API
Scanning (high level)
| Function | Description |
|---|---|
scanFaviconsFromUrl(url, options?) |
GET page HTML → scan link + manifest + synthetic paths |
scanFaviconsFromHtml(html, options?) |
Scan from an HTML string |
scanFaviconsFromManifest(manifest, options?) |
Scan from manifest JSON |
scanFaviconsFromManifestUrl(url, options?) |
GET manifest → scan icons[] |
Attribute extraction (without enrich)
| Function | Description |
|---|---|
faviconAttributesFromHtml(html, options?) |
<link> + manifest icons → FaviconAttributes[] |
faviconAttributesFromManifest(manifest) |
icons[] → FaviconAttributes[] |
faviconAttributesFromManifestUrl(url, options?) |
GET manifest → attributes |
Enrichment and selection
| Function | Description |
|---|---|
scanFaviconsFromAttributes(attrs, options?) |
Resolve URL, dedup, mime/sizes probe, HEAD existence probe |
pickFavicon(favicons) |
Best candidate from FaviconMetadata[] |
faviconPriority(meta) |
Numeric priority (for custom sorting) |
Types
FaviconMetadata—href,rel, optionallysource,mimeType,width,heightFaviconAttributes— raw attributes before scanFaviconSource—"html"|"manifest"|"synthetic"FetchPolicy—"always"|"never"|"if-not-specified"FaviconProbeKind—"page"|"manifest"|"mime-type"|"dimensions"|"existence"FaviconProbeFailureReason—"http-error"|"network-error"|"bad-content-type"|"bad-payload"FaviconProbeFailure— failed probe eventOnProbeFailure—(failure: FaviconProbeFailure) => void
Constants
defaultRelValues— rel tokens for HTML linkdefaultScanPaths— well-known paths forscanFaviconsFromUrldefaultProbeSources—["synthetic"]— sources for HEAD existence probeallProbeSources—["html", "manifest", "synthetic"]manifestIconRel—"manifest"for icons from JSONimageProbeMaxBytes— Range GET limit when determining dimensions (64 KiB)
Scan Options
| Option | Default | Purpose |
|---|---|---|
fetch |
globalThis.fetch |
HTTP client |
pageUrl |
page URL in scanFaviconsFromUrl |
Base for HTML <link href> and FaviconProbeFailure.pageUrl |
manifestUrl |
manifest URL in scanFaviconsFromManifestUrl |
Base for relative src in manifest icons |
onProbeFailure |
— | Callback on failed HTTP probe |
relValues |
defaultRelValues |
Which rel values from HTML to accept |
mimeTypeFetchPolicy |
if-not-specified |
HEAD for content-type |
sizesFetchPolicy |
if-not-specified |
Range GET for width/height |
probe |
["synthetic"] |
HEAD existence filter by source |
scanPaths |
defaultScanPaths |
Synthetic paths in scanFaviconsFromUrl |
seenHrefs |
new Set() |
Dedup across calls |
noDeduplication |
false |
Disable dedup by href |
cheerio |
— | cheerio.load options in scanFaviconsFromHtml |
When scanning from HTML, manifest icons are resolved relative to the manifest URL automatically. For scanFaviconsFromManifest, pass manifestUrl explicitly.
Probe Diagnostics
import { scanFaviconsFromUrl, type FaviconProbeFailure } from "@cutticat/favicon-fetch"
const failures: FaviconProbeFailure[] = []
await scanFaviconsFromUrl("https://example.com", {
onProbeFailure(failure) {
failures.push(failure)
// throw failure — to abort the scan
},
})
FaviconProbeFailure fields:
| Field | Description |
|---|---|
kind |
Probe kind: page, manifest, mime, dimensions, existence |
url |
Request URL |
method |
GET or HEAD |
reason |
Failure reason |
pageUrl |
Source page URL for the scan |
response |
HTTP response, if the request reached the server |
error |
Network or parsing error |
candidate |
Candidate during existence probe |
dropped |
true if the candidate was filtered out (existence HEAD) |
Supported URLs
Allowed:
http:andhttps:(absolute URLs)- relative paths (
/favicon.ico,icons/a.png) — HTML viapageUrl, manifestsrcviamanifestUrl - protocol-relative (
//cdn.example.com/icon.png) — resolved viapageUrlormanifestUrl
Absolute URLs with any other scheme (data:, file:, javascript:, ftp:, etc.) are discarded.
Documentation
Full API documentation is in source JSDoc comments and in dist/index.d.ts.
Requirements
- Node.js >= 22.0.0
- pnpm >= 10.17.0
- TypeScript >= 5.9.0