Degoog Autocomplete
Build your own autocomplete providers to power the search-bar suggestion dropdown. Providers are aggregated, deduplicated, and ranked automatically.
Where providers live
Drop your custom provider into data/autocomplete/ or
whatever you set DEGOOG_AUTOCOMPLETE_DIR to. Each
provider is a file or folder with an entry file that exports a class
or object containing a name and
getSuggestions.
Provider contract
Required:
- name: The display name shown in Settings → Autocomplete.
-
getSuggestions(query, context) (async): Returns a
Promise<AutocompleteSuggestion[]>. The array should contain plain suggestion strings or rich suggestion objects ({ text: string, rich?: RichSuggestion }). Return an empty array if no suggestions are available - never throw.
Optional:
-
settingsSchema and
configure(settings): Declare settings fields and
receive saved values on startup and after every save. Works exactly
like engines and plugins. See
Search engines for the full
SettingFieldshape reference.
The context object
Every call to getSuggestions receives a
context argument. Always use
context.fetch for outbound HTTP so the provider
respects proxy and transport settings.
| Field | Type | Description |
|---|---|---|
context.fetch |
typeof fetch |
A proxy- and transport-aware fetch function. Use this for
every outbound HTTP request instead of the global
fetch.
|
context.lang |
string |
ISO 639-1 base language code (e.g. "en",
"fr", "de"). Derived from the
DEGOOG_DEFAULT_SEARCH_LANGUAGE environment
variable. Defaults to "en" when the variable is
unset.
|
context.useCache |
UseCache |
Async, namespaced TTL cache factory. Call
context.useCache<T>(namespace, defaultTtlMs) to get
a cache with await get, await set,
await delete, and await clear. Shared across
replicas when the optional Valkey sidecar is configured; per-process
otherwise.
|
Rich suggestions
Providers can return entity cards (images, subtitles, and descriptions) that appear at the very top of the autocomplete dropdown. To do this, return an object instead of a string:
export interface RichSuggestion {
description?: string;
thumbnail?: string;
type?: string;
}
export type AutocompleteSuggestion = string | { text: string; rich?: RichSuggestion };
Plain string suggestions are fetched on the server via
context.fetch (which respects the provider's outgoing
transport). When you include rich.thumbnail, return the
upstream image URL from getSuggestions. Degoog rewrites it
to a signed /api/proxy/image URL before the suggest API
responds, so the dropdown loads the image through the server.
Declaring client exposure
The settings page shows a network indicator on plugin cards when an
extension declares isClientExposed. Autocomplete providers
do not use that field. Return upstream thumbnail URLs as usual; Degoog
signs them for the image proxy when suggestions are merged.
| What you return | Server requests | Browser requests |
|---|---|---|
| Plain strings only | Your provider uses context.fetch to call upstream APIs. |
None from the provider itself. |
Rich suggestions with thumbnail |
Your provider fetches suggestions; Degoog signs each thumbnail for /api/proxy/image. |
The browser loads the signed proxy URL only. Degoog fetches the upstream image on the server. |
You do not need to call signProxyUrl or build proxy URLs
yourself in a provider. Omit rich.thumbnail entirely if you
do not want image cards in the dropdown.
async getSuggestions(query, context) {
const doFetch = context?.fetch ?? fetch;
const res = await doFetch(`https://example.com/suggest?q=${encodeURIComponent(query)}`);
const items = await res.json();
return items.map((item) => ({
text: item.title,
rich: {
description: item.subtitle,
thumbnail: item.imageUrl,
},
}));
}
async getSuggestions(query, context) {
const doFetch = context?.fetch ?? fetch;
const res = await doFetch(`https://example.com/suggest?q=${encodeURIComponent(query)}`);
const items = await res.json();
return items.map((item) => item.title);
}
Minimal example
export default class MyAutocompleteProvider {
name = "My Provider";
async getSuggestions(query, context) {
if (!query.trim()) return [];
const doFetch = context?.fetch ?? fetch;
const encoded = encodeURIComponent(query.trim());
try {
const res = await doFetch(
`https://example.com/suggest?q=${encoded}`,
{ headers: { Accept: "application/json" } },
);
if (!res.ok) return [];
const data = await res.json();
// OpenSearch format: [query, [suggestion, suggestion, ...]]
if (Array.isArray(data) && Array.isArray(data[1])) {
return data[1].map(String).filter(Boolean);
}
return [];
} catch {
return [];
}
}
}
OpenSearch compatibility
The /api/suggest/opensearch endpoint returns results in
the
OpenSearch Suggestions 1.1
format: [query, [suggestion, suggestion, ...]]. This
means your browser address bar can use Degoog directly as a search
engine with autocomplete. Custom providers do not need to do
anything special - the registry wraps all results automatically.
Aggregation and scoring
When multiple providers are enabled, their suggestions are fetched in parallel and merged using a round-robin interleave. Duplicates are collapsed into a single suggestion that shows all contributing sources. The merged list is capped at 10 items.
Each provider has a Score field under the
Advanced section in Settings → Autocomplete.
Higher scores mean that provider's suggestions appear earlier in the
merged list. The default score is 1.
Outgoing transport
Like engines, every provider automatically gets an
Outgoing HTTP client selector under
Advanced. This can be set to
fetch, curl, curl-fallback,
or any custom transport you have
installed. Always use context.fetch in your provider so
this setting applies automatically.
Settings example
export default class MyAutocompleteProvider {
name = "My Provider";
settingsSchema = [
{
key: "apiKey",
label: "API Key",
type: "password",
secret: true,
required: false,
description: "Optional API key for authenticated requests.",
},
];
apiKey = "";
configure(settings) {
this.apiKey = typeof settings.apiKey === "string" ? settings.apiKey.trim() : "";
}
async getSuggestions(query, context) {
if (!query.trim()) return [];
const doFetch = context?.fetch ?? fetch;
const encoded = encodeURIComponent(query.trim());
const headers = { Accept: "application/json" };
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
try {
const res = await doFetch(`https://example.com/suggest?q=${encoded}`, { headers });
if (!res.ok) return [];
const data = await res.json();
return Array.isArray(data) ? data.map(String).filter(Boolean) : [];
} catch {
return [];
}
}
}
Language
context.lang is always set to a two-letter ISO 639-1
code. It is derived from the
DEGOOG_DEFAULT_SEARCH_LANGUAGE environment variable
(e.g. en-GB becomes en). If the variable
is not set, context.lang defaults to
"en". Use it to pass language or market parameters to
your upstream API.
async getSuggestions(query, context) {
const lang = context?.lang ?? "en";
const mkt = `${lang}-${lang.toUpperCase()}`; // e.g. "en-EN", "fr-FR"
const res = await (context?.fetch ?? fetch)(
`https://example.com/suggest?q=${encodeURIComponent(query)}&lang=${lang}`,
);
// ...
}
Setup
Create your data/autocomplete/ directory or set
DEGOOG_AUTOCOMPLETE_DIR. Add a single file like
my-provider.js or a folder with an
index.js. The provider ID will be the filename or
folder name prefixed with autocomplete-.
If you plan on distributing your provider through the
Store, add it under the
autocomplete key in your store's
package.json manifest alongside a
screenshots/ folder so the Store card looks great.
Built-in providers
Degoog ships two built-in providers that are always available and cannot be uninstalled:
- Google - uses the Firefox suggest endpoint.
- DuckDuckGo - uses the public DuckDuckGo autocomplete API.
Additional providers (Brave, Bing, Yahoo) are available from the official store under the Autocomplete category.