Degoog Favicon providers
Write a provider that tells Degoog where to find the icon for a domain. Core fetches the icon, checks it and serves it.
What favicon providers are
Core has no built-in favicon source. Every icon on the results page comes from a favicon extension installed from the Store.
Degoog gives your provider a hostname. You return a URL, the icon bytes, or
null. Core fetches and checks the icon, and the browser loads it
from a signed /api/proxy/favicon URL on the same instance.
Where providers live
Installed providers go in data/favicon/, or wherever
DEGOOG_FAVICON_DIR points. Each provider is a folder with an
entry file named index.js, index.ts,
index.mjs or index.cjs. The entry exports a class
or an object with a name and a getFavicon
function.
The loader looks for the default export, then
provider, then Provider. It skips anything without
a string name and a getFavicon function.
Provider contract
Two members are required. name is the label in Settings ->
Favicons. getFavicon(host, context) is async and returns a
FaviconResult. host is a bare hostname like
example.com, with no scheme or path.
Return null on a miss instead of throwing. Core catches thrown
errors, but it logs every one.
description, settingsSchema and
configure(settings) are optional and work as they do on
engines. Search engines has the
SettingField reference.
export type FaviconResult =
| { url: string }
| { data: Uint8Array; contentType: string }
| null;
export interface FaviconProvider {
name: string;
description?: string;
settingsSchema?: SettingField[];
configure?(settings: Record<string, unknown>): void;
getFavicon(host: string, context: FaviconContext): Promise<FaviconResult>;
}
The three return shapes
| You return | What core does with it | Use it for |
|---|---|---|
{ url: string } |
Fetches the URL on the server and validates the bytes. If the fetch or the check fails, asks the next provider. | Services with one icon URL per domain, like the official Google and DuckDuckGo providers. |
{ data: Uint8Array, contentType: string } |
Validates the bytes you passed. |
Icons you generate, like identicons, or icons you fetched yourself
with context.fetch, for example because the upstream
needs an auth header.
|
null |
Asks the next provider in the list. | Any host you know nothing about. |
The context object
Every call to getFavicon gets a context argument.
| Field | Type | Description |
|---|---|---|
context.fetch |
typeof fetch |
Use it instead of the global fetch. It applies the proxy
settings and this provider's Outgoing HTTP client.
|
context.userAgent |
string |
The user agent Degoog sends on outgoing requests. |
context.size |
number |
The icon size core asks for, in pixels. It is 64. Pass it
to services that take a size. Returning a smaller icon is fine, core
keeps whatever size you return.
|
context.useCache |
UseCache |
context.useCache<T>(namespace, defaultTtlMs) returns
an async TTL cache with get, set,
delete and clear. It lives in Valkey when
that is configured, in process memory otherwise. Core already caches
the final icon, so use this for your own lookups, like an API token.
|
How core checks the bytes
- Core accepts PNG, ICO, GIF, JPEG and WebP. It rejects SVG, because SVG can carry scripts, so convert SVG icons to PNG.
-
Core reads the format from the file's first bytes and ignores the
Content-Typeheader, yours or the upstream's. - Icons over 64KB are rejected.
-
Core fetches a
{ url }result with a 5 second timeout and checks the resolved address on every redirect, so a URL that points at127.0.0.1or a private network fails.
A failed check counts as null, and core asks the next
provider.
Priority and the provider chain
Core asks providers one at a time and stops at the first icon that passes validation. Unlike autocomplete, results are not merged.
The order comes from the reserved priority settings key.
Higher goes first, and ties fall back to folder order. Providers with no
priority yet, such as one just installed, go after all ranked ones.
Instance owners set the order by dragging cards in Settings -> Favicons.
Core skips providers with the reserved disabled key. Your
provider doesn't need to handle either key.
When the indexer's favicon store is on, core checks it before any provider.
Each getFavicon call gets 5 seconds, set by
DEGOOG_FAVICON_PROVIDER_TIMEOUT_MS. A provider that runs over
counts as null.
If every provider returns null, the result shows the domain's
first letter. If no provider is enabled and the favicon store is off,
results show a "No favicon extension installed" placeholder and the
browser requests no icons.
Core caches the final answer, icon or miss, so your provider is asked about a given host rarely, not on every search.
Outgoing transport
Every favicon provider gets an Outgoing HTTP client
selector under Advanced, stored in the reserved
outgoingTransport key. Options are fetch,
curl, curl-fallback, or any installed
transport. It applies to
context.fetch only, not to core's fetch of a
{ url } result.
IDs, folders and the manifest
The ID, which is also the settings key, is the install folder name plus
-favicon. Store installs name the folder after the repo and
item, giving {author}-{repo}-{item}-favicon. The official
Google provider is
degoog-org-official-extensions-google-favicon.
In a store repository, providers live under
favicon/<name>/ and are listed under the
"favicon" key of the root package.json.
my-degoog-repo/
package.json
favicon/
my-provider/
index.js
author.json
README.md
screenshots/
{
"favicon": [
{
"path": "favicon/my-provider",
"name": "My Favicon Provider",
"description": "Icons from icons.example.com."
}
]
}
author.json, README.md and
screenshots/ are optional. Other extensions can depend on your
provider with a dependency URL ending in favicon/<name>.
Store repositories covers
the rest of the manifest.
Minimal example with a URL
export default class MyFaviconProvider {
name = "My Favicon Provider";
description = "Icons from icons.example.com.";
async getFavicon(host, context) {
if (!host) return null;
return {
url: `https://icons.example.com/${encodeURIComponent(host)}?size=${context.size}`,
};
}
}
The provider only builds the URL. If it returns a 404 or something that isn't an image, core asks the next provider.
Minimal example with a generated icon
This one returns a 1x1 GIF coloured from a hash of the hostname, with no network calls.
const GIF_HEAD = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00];
const GIF_TAIL = [
0x00, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x02, 0x02, 0x44, 0x01, 0x00,
0x3b,
];
const hashHost = (host) => {
let hash = 0;
for (const char of host) hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
return hash;
};
export default {
name: "Colour Swatch",
description: "A tiny colour per domain, no network needed.",
async getFavicon(host) {
if (!host) return null;
const hash = hashHost(host);
const rgb = [(hash >> 16) & 0xff, (hash >> 8) & 0xff, hash & 0xff];
return {
data: new Uint8Array([...GIF_HEAD, ...rgb, ...GIF_TAIL]),
contentType: "image/gif",
};
},
};
Generated bytes go through the same validation as fetched ones.
Settings example
export default class MyFaviconProvider {
name = "My Favicon Provider";
settingsSchema = [
{
key: "apiKey",
label: "API Key",
type: "password",
secret: true,
required: true,
description: "Key for the icon service.",
},
];
apiKey = "";
configure(settings) {
this.apiKey = typeof settings.apiKey === "string" ? settings.apiKey.trim() : "";
}
async getFavicon(host, context) {
if (!host || !this.apiKey) return null;
try {
const res = await context.fetch(
`https://icons.example.com/v1/${encodeURIComponent(host)}`,
{ headers: { Authorization: `Bearer ${this.apiKey}`, "User-Agent": context.userAgent } },
);
if (!res.ok) return null;
const contentType = res.headers.get("content-type") ?? "";
return { data: new Uint8Array(await res.arrayBuffer()), contentType };
} catch {
return null;
}
}
}
The upstream needs an auth header, so the provider fetches the bytes with
context.fetch and returns { data }. Core still
reads the format from the bytes, so a wrong content-type does
no harm.
Setup
Add a folder with an index.js to data/favicon/ or
DEGOOG_FAVICON_DIR and restart Degoog. Then enable the
provider and drag it into place in Settings -> Favicons.
The official store has Google, DuckDuckGo, Kagi, Yandex, Site favicon (experimental) and Identicon, which generates a 64px icon offline. Google and DuckDuckGo are short enough to use as references.