Skip to content

Discover Favicons ​

Feedscout can discover favicon URLs from webpages and feeds. Favicons are site icons used in browser tabs, bookmarks, and feed readers.

Basic Usage ​

typescript
import { discoverFavicons } from 'feedscout'

const favicons = await discoverFavicons('https://example.com')

Each result contains the favicon URL and validation status. A URL is valid when the response is 2xx and is an image, by its Content-Type or by its content:

typescript
{
  url: 'https://example.com/apple-touch-icon.png',
  isValid: true,
  method: 'html',
}

By default, all discovery methods are used (platform, feed, html, headers, guess). You can customize which methods to use:

typescript
const favicons = await discoverFavicons('https://example.com', {
  methods: ['html', 'headers', 'guess'],
})

Untrusted SVG favicons

A favicon validated as an SVG is returned by URL only. Its contents are not sanitized. SVG files can carry active content (e.g. <svg onload="...">), so treat returned SVG favicon URLs as untrusted: render them as <img src> (which neutralizes scripts), not as inlined markup, or sanitize before use.

Discovery Methods ​

Favicons use the same discovery pipeline as feeds. See the Feeds section for details on how each method works.

MethodWhat It Looks For
PlatformAvatars/icons from known platforms (GitHub, Mastodon, Bluesky, etc.)
Feed<icon> in Atom feeds, itunes:image podcast artwork in RSS and Atom feeds, favicon/icon in JSON Feeds
HTML<link> tags with rel="icon", rel="shortcut", rel="alternate icon", rel="apple-touch-icon"
HeadersLink headers with icon-related rel values
GuessCommon paths like /favicon.ico, /apple-touch-icon.png

Supported Platforms ​

The Platform method extracts avatars and icons directly from known platforms using their public APIs or URL conventions:

PlatformWhat It ExtractsMethod
AmebloBlogger profile imagePage HTML
Are.naUser avatar, or the channel owner's avatarPage HTML, or the public API through an enricher
BehanceProfile avatarPage HTML
BitChuteChannel imagePage HTML
BlueskyProfile avatarPublic API, through an enricher
BookWyrmUser avatarPage HTML, or the user's actor JSON through an enricher for shelf and other user pages
DailymotionUser avatar, or the playlist owner's avatarPublic API, through an enricher
Dev.toProfile or organization image, also on article pagesPublic API, through an enricher
DeviantArtUser avatarURL pattern
ExblogBlog logoPage HTML, or the blog's top page through an enricher
FlickrUser buddyicon, also on favorites, albums and galleries pagesPage HTML
Gitea (Codeberg, gitea.com, self-hosted)User avatarURL pattern
GitHubUser avatarURL pattern
GitHub GistUser avatarURL pattern
GitLabUser or group avatarPublic API, through an enricher
GoodreadsUser avatarPage HTML, or the user page through an enricher
HabrHub icon, user avatar or company logoPage HTML
Hatena BookmarkUser avatarURL pattern, checked through an enricher
Lemmy (self-hosted)Community icon or user avatarPage HTML, or the public API through an enricher
LetterboxdMember avatarPage HTML, or the member's films page through an enricher
LobstersUser avatarURL pattern
MastodonProfile avatarPublic API, through an enricher
MediumProfile avatar or publication icon, also on subdomainsPublic feed, through an enricher
Micro.blogUser avatarURL pattern
MyAnimeListUser avatarPage HTML, or the profile page through an enricher for list and history pages
Naver BlogBlog profile picturePage HTML, and the mobile page through an enricher for desktop blog pages and post pages
NebulaChannel avatarPage HTML, or the content API through an enricher
noteUser avatar, or the magazine or article owner's avatarPage HTML, Public API through an enricher
ObservableUser avatar, or the collection or notebook owner's avatarPublic API, through an enricher
OdyseeChannel avatarPublic API, through an enricher
PeerTube (self-hosted)Channel or account avatarPage HTML, or the public API through an enricher
PinterestProfile avatarPage HTML, or the profile page through an enricher for board and other user pages
Pixelfed (self-hosted)Profile avatarPage HTML, or the public API through an enricher when the page has no avatar
PostypeChannel avatarPage HTML, or the channel page through an enricher for post pages and channel subdomains
Product HuntProduct logo, or topic image cropped to a squarePage HTML
RedditSubreddit icon or user avatarPublic API, through an enricher
SoundCloudUser avatarPage HTML
SourceForgeProject iconURL pattern
SourceHutUser avatar, or the owner's avatar on repository pagesPage HTML, the owner's page through an enricher
SteamGame icon or group avatarPage HTML, or the public API through an enricher
TogetterUser avatarPage HTML
TumblrBlog avatarURL pattern
VelogUser avatarPage HTML, or the GraphQL API through an enricher
YouTubeChannel avatar, also on watch, youtu.be and live pagesPage HTML
ZennProfile, publication or topic iconPage HTML

Enriching Platform Icons ​

A platform handler reads only the page URL and the page content. On some platforms the icon takes an extra request to reach, such as a call to the platform's API. For those pages the handler returns a DiscoverRef naming the platform and the account, and discovery hands each ref to enrichFn.

By default, enrichFn runs the built-in enrichers in defaultFaviconEnrichers, making their requests through discovery's fetchFn. Set enrichFn: false to make no extra request, in which case refs are dropped:

typescript
const favicons = await discoverFavicons(url, {
  enrichFn: false,
})

To pick the enrichers, build the function with createEnrichFaviconFn and the enrichers you want. Each built-in enricher is exported on its own, such as mastodonEnricher. A function you build this way makes its requests through the fetchFn you pass it, not discovery's, so pass the same one to both:

typescript
import { createEnrichFaviconFn, mastodonEnricher } from 'feedscout/favicons'

const favicons = await discoverFavicons(url, {
  fetchFn: myCustomFetch,
  enrichFn: createEnrichFaviconFn({ enrichers: [mastodonEnricher], fetchFn: myCustomFetch }),
})

The addresses it returns are validated like any other platform candidate. You can pass your own function too, for example one that answers from a cache:

typescript
const favicons = await discoverFavicons(url, {
  enrichFn: (ref) => {
    return cache.get(`${ref.platform}:${ref.id}`)
  },
})

Extracting Icons from Feeds ​

When given a feed URL, favicon discovery can extract icons directly from the feed content. Atom feeds provide an <icon> element, podcast feeds in RSS or Atom provide their square itunes:image artwork, and JSON Feeds include favicon and icon fields. The RSS <image> element is not used, since it is a channel logo that is usually wide:

typescript
// Pass a feed URL to extract its icon
const favicons = await discoverFavicons('https://example.com/feed.xml')

// Or provide existing feed content
const favicons = await discoverFavicons({
  url: 'https://example.com/feed.json',
  content: feedContent,
})

Configuration ​

Customize discovery the same way as feeds:

typescript
const favicons = await discoverFavicons(url, {
  methods: {
    html: true,
    guess: {
      uris: ['/favicon.ico', '/icon.svg'],
    },
  },
})

Default Guess Paths ​

typescript
import { defaultGuessPaths } from 'feedscout/favicons'

// [
//   '/favicon.ico',
//   '/apple-touch-icon.png',
//   '/apple-touch-icon-precomposed.png',
//   '/favicon.png',
//   '/favicon.svg',
// ]

Favicon discovery looks for these rel values in HTML <link> tags and HTTP Link headers:

typescript
import { defaultIconRels, linkSelectors } from 'feedscout/favicons'

// ['icon', 'shortcut', 'alternate icon', 'apple-touch-icon', 'apple-touch-icon-precomposed']