Where engines live
Custom engines live in data/engines/ (or DEGOOG_ENGINES_DIR). Each file (or folder with an entry file, depending on how the app loads them) exports a SearchEngine object.
SearchEngine contract
Required:
- name (string) — Display name in Settings → Engines.
- executeSearch(query, page?, timeFilter?, context?) (async) — Returns
Promise<SearchResult[]>. The optionalcontextprovidescontext.fetch; use it for all outbound HTTP requests so the engine respects the user’s proxy setting (Settings → General).
SearchResult shape:
{
title: string,
url: string,
snippet: string,
source: string,
thumbnail?: string,
duration?: string
}
Optional:
- bangShortcut (string) — Enables
!shortcut queryto search this engine only (e.g.bangShortcut: "ex"→!ex linux). - settingsSchema (SettingField[]) — Configurable fields; they appear in Settings → Engines with a Configure modal.
- configure(settings) — Called on startup (if settings exist) and when the user saves in the UI.
SettingField (same as plugins):
{
key: string,
label: string,
type: "text" | "password" | "url" | "toggle" | "textarea",
required?: boolean,
placeholder?: string,
description?: string,
secret?: boolean
}
Non-web search types
To export an images or video engine, add a named type export:
export const type = "images"; // or "videos" or "web" (default)
Proxies (proxy-friendly engines)
When users enable a proxy for search (Settings → General), all engine requests can go through that proxy. For your engine to be proxy-correct:
- Use the injected fetch. Implement
executeSearch(query, page?, timeFilter?, context)and usecontext.fetchfor every outbound HTTP request. If you use globalfetch, your requests will not use the user’s proxy. - Declare outgoing hostnames. Export a named
outgoingHostsarray of hostname strings (no protocol or path). Those hostnames are added to the proxy allowlist so your engine works when the proxy is on. Use["*"]only if your engine fetches from arbitrary user-configured URLs (e.g. RSS feeds).
Example (semantically correct for proxies):
export const outgoingHosts = ["www.example.com", "example.com"];
export default class MyEngine {
name = "My Search";
async executeSearch(query, page = 1, _timeFilter, context) {
const url = `https://www.example.com/search?q=${encodeURIComponent(query)}`;
const doFetch = context?.fetch ?? fetch;
const response = await doFetch(url, {
headers: { "User-Agent": "my-engine/1.0" },
});
const html = await response.text();
// ... parse and return results
}
}
Always use const doFetch = context?.fetch ?? fetch; then doFetch(url, opts) so that when context is provided (it is when the app runs your engine), requests go through the user’s proxy when enabled.
Setup
Create data/engines/ (or set DEGOOG_ENGINES_DIR). You can add either a single file (e.g. my-engine.js) or a folder with index.js (or .ts, .mjs, .cjs). The engine id is derived from the filename (without extension) or folder name, with an engine- prefix (e.g. my-engine.js or my-engine/index.js → id engine-my-engine).
Distributing via Store
When distributing an engine via a store repository, add a screenshots/ folder inside the engine folder for the Store card thumbnail and lightbox.
How settings work
- Declare
settingsSchema— a Configure button appears in Settings → Engines. - User saves; values are stored in
data/plugin-settings.json. configure(settings)is called after save and on server restart if settings exist.- Return an empty array from
executeSearchwhen required settings are missing so the engine contributes no results.
News
News search is not an engine type: the News tab uses RSS feeds configured in Settings → Engines → News. Feed URLs are stored in data/plugin-settings.json.