twentyhq/favicon: The Favicon Service That Treats Logos Like a Search Problem

An open-source deep dive into how a tiny NestJS service hunts through HTML clues, normalizes messy brand icons, and turns them into predictable, self-hostable assets.

8 min read • View on GitHub • More from twentyhq

A browser page, HTML fragments, a manifest sheet, and a /favicon.ico sign all point toward one square logo. The scene explains that favicon retrieval is a discovery problem, not a simple file lookup, because the web scatters brand marks across multiple clues.
The service starts where the web starts, with clues, fallbacks, and competing hints about what the logo should be.
Key Takeaways

The internet does not hand you a favicon. It hands you clues.

A favicon is rarely a file. It is a scavenger hunt across HTML tags, manifest hints, legacy paths, and fallback services. `twentyhq/favicon` is interesting because it refuses the fiction that a logo lives in one obvious place.

That matters in product surfaces where the logo must be there before the user uploads anything. A CRM, an onboarding flow, or an admin console does not want a mystery box. It wants one clean asset, at a known size, every time.

Born inside Twenty, then opened up as infrastructure

The repo grew out of a practical need inside Twenty, the open-source CRM. The team wanted a reliable way to pull a company's logo from a domain, normalize it, and serve it in sizes that would not crumble in the UI. The README frames the goal set plainly: reliability, precision, quality, and open source.

We needed a solution with four main qualities: 1. Reliability to prevent downtime. 2. Precision in delivering specific icon sizes, optimizing user bandwidth. 3. High-quality resolution, offering the finest available icon for any requested size. 4. Open-source availability, as it's a universal requirement and should be accessible for contributions and hosting.

Jean Eudes Nallatamby, Author/Creator · DEV Community post

That is the right brief for a utility API. The job is not to be clever. The job is to be boring in the best possible way: predictable, inspectable, and good enough to sit under a product feature without creating support work.

Discovery is the product

The most interesting part of the repo is the discovery layer in `src/favicon/url-fetcher/`. It does not start by assuming `/favicon.ico` is the answer. It collects candidates from the page itself, including `link[rel="icon"]`, `link[rel="apple-touch-icon"]`, and `meta[itemprop="image"]`, then moves through fallback paths when the page gives up too little.

That turns the problem into ranking. The service is not asking "where is the favicon?" It is asking "which clue should win?" The answer is usually the least bad candidate, not the first candidate that happens to exist.

The service behaves like a ranked search flow, not a direct file fetch. Discovery, fallback, and normalization are separate steps, and that separation is the whole point.

Normalization is where the real engineering lives

Once a candidate is found, the service has to make it useful. The pipeline uses `sharp` and `ico-to-png` to turn different source formats into a consistent PNG buffer, then resizes that buffer into a fixed set of supported sizes, including 16, 32, 64, 128, 180, and 192 pixels.

A near-square logo passes through a rigid stencil while a long banner-shaped logo is held outside the opening. The image explains why the service prefers almost-square assets and rejects wide marks that would behave badly as favicons.
The service chooses icons that behave well in product UI, not just icons that exist.

That fixed-size output is a product decision, not just a technical one. SVG could stay vector, and the service still chooses a normalized raster path because consistency is the promise. It is easier to cache, easier to serve, and less likely to surprise the consumer that expects a square image, not a shape-shifting asset.

The repo also draws a hard line with its geometry filter. It rejects assets that are not almost square, which avoids accidentally promoting a banner logo into a favicon slot. That bluntness is the point. The service is optimizing for UI reliability, not archival purity.

How it stacks up against the rest

There is a useful distinction here. Some tools generate favicon bundles for your own site. Others fetch logos for other companies. `twentyhq/favicon` belongs in the second camp, and that makes it a better fit for B2B products that need to identify a customer or prospect by domain.

ServiceSelf-hostableWhat it optimizes forOutput styleBest fit
twentyhq/faviconYesRanked discovery plus normalizationStable PNG sizesB2B apps and internal platforms
Google Favicon APINoFast fallback lookupOpaque, best-effortQuick favicon retrieval
Clearbit Logo APINoCompany logo enrichmentPolished but black boxSaaS onboarding and enrichment
BrandfetchNoBroader brand asset coverageRich brand profilesMarketing and CRM enrichment
FaviconKitNoSimple favicon lookupUtility API styleLightweight external fetches

The adjacent Node module `favicons` solves a different problem entirely. It generates a favicon package from your own source image. `twentyhq/favicon` is a service that goes hunting on the open web, then turns whatever it finds into something dependable.

The bigger lesson: utility APIs are becoming infrastructure you own

This repo is small, but the pattern is bigger than favicons. Teams are increasingly replacing opaque utility APIs with self-hostable services that live inside their own trust boundary. That shift matters when the feature is tiny but the operational cost of being wrong is high.

That is why this project feels more important than its size suggests. It takes a messy internet problem, imposes a clear contract, and gives product teams a service they can reason about. The code is doing infrastructure work, but the editorial insight is simpler: the most valuable API is often the one that makes uncertainty disappear.