besticon: a favicon API that generates an icon when a site has none
Favicon service written in Go
At a glance
- What is it?
- besticon is a Go service with two HTTP endpoints that find a site icon or draw one when it is missing, plus a letter icon fallback whose color comes from the site itself. The interesting engineering is in the size range matching and the fallback path, and the Docker image is explicitly less tested than the hosted demo.
- Who is it for?
- Adopt besticon when your product needs one consistent icon source for many third-party URLs and you would rather not maintain a scraper, since the size range matching and the generated fallback are the parts that take real work.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Finding a site icon became a parsing problem, not a file path
The service exists because of a small historical accident. Websites used to have a favicon.ico, or not, and the introduction of apple-touch-icon.png made finding the icon for a site considerably more complicated, because a site may offer several icons of different sizes and formats and expect the consumer to pick. besticon takes that problem away. It fetches a site, locates the icon candidates, and returns one image for a URL you hand it, always returning an icon image: it redirects to an official icon if possible, and creates and returns a fallback image when it is not. The intended caller is a product that displays a list of third-party services, a bookmark dashboard, a link preview component, anything that needs a small square image for an arbitrary domain. The written-out example is a site that has no icon at all, and the result is still an image, which is the property that matters to a frontend developer. The upstream code is Go, with a module path of github.com/mat/besticon/v3 and a go directive of 1.26, and the repository is split into packages that map onto the pipeline: besticon for the core logic, ico for the icon container format, lettericon for the generated fallback, colorfinder for picking a color, and vcr for recording and replaying HTTP interactions during tests.
The size parameter is a range, not a number
The API design decision worth understanding is the size parameter, and it is unusual. Instead of asking for 32 pixels, you give a range written as min..perfect..max, for example 32..50..100. The meaning is a preference order: if no image of size perfect..max can be found, and none of perfect..min can be found either, the service generates a fallback icon instead. So a request for 32..50..100 will take a 64 pixel icon if that is what the site offers, will accept something in the 50 to 100 band, and will also accept something between 32 and 50 as a last resort before giving up and drawing one. A tighter request such as 64..64..120 declares that 64 is the ideal and that anything outside 64..120 is unusable, so a site offering only a 32 pixel icon forces the fallback. This turns the classic favicon problem, where every client hard codes its own preferred size, into a single server side policy that all clients share. The formats parameter follows the same idea, accepting a comma-separated list, with a default of gif,ico,jpg,png,svg. Two more parameters shape the fallback rather than the search: fallback_icon_url redirects to an image you provide when nothing suitable is found, and fallback_icon_color takes a hex value such as ff0000 to color letter icons instead of leaving them grey when no color can be found for the site.
Letter icons and the colorfinder package
When the service cannot find an icon, it draws one. The lettericon package produces a letter icon, and the sample URL in the project documentation uses a Cyrillic domain, фминобрнауки.рф, to show a ф rendered as the icon. That example is doing more than showing off a non-Latin script. It exposes the known limitation the project lists first under bugs and limitations, poor i18n support for letter icons, tracked as issue 13, so the letter fallback is a well known weak spot rather than an advertised feature. The colorfinder package sits next to it and answers a different question: what color should this generated icon be. The fallback_icon_color parameter exists precisely because that search can come up empty, in which case the letter is drawn grey. A self hosted operator therefore has two ways to control the look of a generated icon, a fixed hex color applied across all sites, or your own fallback_icon_url pointing at an image of your own. The interesting design constraint is that the service wants to be the single source of truth for icon appearance, and these two parameters are the escape hatches for when its guesses do not match your design system.
A second endpoint returns every candidate instead of choosing
The other endpoint exists for a different kind of consumer. GET /allicons.json returns all icons for a given site rather than one selected image, and the same url and formats parameters apply, with a default of png,ico,gif,jpg for this endpoint. The documented examples are for github.com, once with no format filter and once restricted to png. The difference in approach between the two endpoints is the difference between a widget and a picker. GET /icon is a policy decision baked into a URL, and it always answers with an image, which means a template can drop it into an img tag without checking anything. GET /allicons.json hands back a set, which means the caller has to rank them itself. That is the right choice for a settings screen where a user chooses the icon for a saved service, or for a tool that needs to compare candidates. The two endpoints also behave differently on the fallback path, since the first is specified to always produce something, while the second is described simply as returning all icons for a site. If you are building a picker, the letter icon fallback still needs a place to live, and the /icon endpoint with a size range is the way to get it.
Running it yourself: binary, container, or a one-button deploy
Deployment is the part of the project with the most options. Prebuilt binaries for some operating systems are published on the releases page, and the source can be fetched with go get if you have Go installed, exactly as the documentation shows:
$ go get -u github.com/mat/besticon/v3The Makefile in the repository offers the local development loop, with a build target and a test target that runs each package separately:
build:
go build ./...test:
go test -v github.com/mat/besticon/v3/ico
go test -v github.com/mat/besticon/v3/besticon
go test -v github.com/mat/besticon/v3/besticon/iconserver
go test -v github.com/mat/besticon/v3/lettericon
go test -v github.com/mat/besticon/v3/colorfinderThe container image is published as matthiasluedtke/iconserver on Docker Hub and is generated from the Dockerfile in the repository, which the project says it tries to keep updated for every release. The important caveat is the next sentence: that docker image is not used to run icons.better-idea.org and therefore is not well tested. Read that as a warning about the image rather than about the code. The Dockerfile is a two stage build that compiles in an official Golang image and copies a static binary into an alpine image, so the runtime layer carries no Go toolchain. For hosted deployments the project lists Render, Heroku and Google Cloud Run, and the repository carries a Procfile, an app.json and a render.yaml, which are the conventional files those platforms read.
Configuration is a list of environment variables in the container
The Dockerfile documents the configuration surface more precisely than any prose would, because the environment variables are written out as defaults. The service listens on PORT=8080, and SERVER_MODE=redirect selects the default behaviour, which matches the endpoint contract of redirecting to an official icon when one exists. CORS is off by default, with CORS_ENABLED, CORS_ALLOWED_HEADERS, CORS_ALLOWED_METHODS, CORS_ALLOWED_ORIGINS, CORS_ALLOW_CREDENTIALS and CORS_DEBUG as separate switches, and that default matters if you call the service from a browser rather than from your own server. The fetch behaviour is governed by HTTP_CLIENT_TIMEOUT=5s, HTTP_MAX_AGE_DURATION=720h and HTTP_USER_AGENT, and the last one is a good sign of care, since many sites serve different markup to unknown clients. CACHE_SIZE_MB=32 sets the local cache budget, and HOST_ONLY_DOMAINS=* is a filter for which hosts the service will fetch. The Makefile adds a run_server target for local work, with its own variables RUN_SERVER_PORT, RUN_SERVER_SERVE_ASSETS_FROM_DISK and a DEMO_SITES list, which is a convenient way to try the service against a handful of well known domains.
Observability is built in, and the release history has a gap
Prometheus metrics are exposed under /metrics, and a Grafana dashboard configuration based on those metrics is published in the repository as grafana-dashboard.json, so the operational baseline is a repository artifact rather than something you have to assemble. The project is not archived and the last push was on 2026-09-06, the same day as the v3.23.0 release. The release history before that is uneven: v3.22.0 came on 2026-02-18 and v3.21.0 on 2024-11-03, so there is a stretch of more than a year between two tagged versions, which tells you that a good number of changes ship on the default branch without a tag. The dependency set is small and readable, which suits a service whose whole job is making outbound HTTP requests: goquery for parsing HTML, golang/freetype for the letter icon rendering, groupcache for the cache layer, prometheus/client_golang for the metrics, rs/cors for the CORS layer, and golang.org/x/image and golang.org/x/net for the image and network work. The license is MIT, which for a service you deploy yourself means you can read the code, modify it, and run a modified version, provided you keep the copyright and licence notice. The project also keeps a NOTICES file and a notices-more directory alongside the.licensed.yml configuration, so third party attribution is handled rather than ignored.
Editorial conclusion
Adopt besticon when your product needs one consistent icon source for many third-party URLs and you would rather not maintain a scraper, since the size range matching and the generated fallback are the parts that take real work. Do not adopt it for a one-off lookup, where fetching the site and reading its own icon tag is cheaper, and do not assume the published Docker image matches the demo instance, because the project says that image is not what runs the demo and is therefore not well tested. Verify first that your server can reach the target sites, that the fallback_icon_color and letter icon behaviour suit your interface, and that the CORS settings in the container environment variables are set for the browser origins you intend to call from.
Frequently asked questions
How do I get an icon for a website with besticon?
Call GET /icon with a url parameter and a size range written as min..perfect..max, for example https://icons.better-idea.org/icon?url=yelp.com&size=32..50..120. The endpoint always returns an icon image, redirecting to an official icon if one can be found and generating a fallback image if not.
What happens when a site has no favicon?
The service generates a fallback letter icon instead. You can control it with fallback_icon_url, which redirects to an image you supply, or with fallback_icon_color, which takes a hex value such as ff0000 to color the letter rather than leaving it grey. The project lists poor i18n support for letter icons as a known limitation.
How do I self host besticon?
Download a binary from the releases page, build from source with Go, or use the Docker image matthiasluedtke/iconserver on Docker Hub, which is generated from the Dockerfile in the repository. The project notes that this image is not the one running the hosted demo and is therefore not well tested, and it also lists Render, Heroku and Google Cloud Run as deployment options.
Does besticon expose metrics for monitoring?
Yes. Prometheus metrics are exposed under the /metrics path, and a Grafana dashboard configuration based on those metrics is published in the repository as grafana-dashboard.json.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mat-besticon)