Degoog — Plugins

Bang commands, slots, search result tabs, search bar actions, routes, and middleware.

What plugins are

Plugins live in data/plugins/ (or DEGOOG_PLUGINS_DIR). Each plugin is a folder with an entry file. One plugin can expose several capabilities: a bang command, a slot, search bar actions, HTTP routes, and/or request middleware.

Folder structure

data/plugins/
  my-plugin/
    index.js        # required — entry point
    template.html   # optional
    style.css       # optional
    script.js       # optional
    card.html       # optional — read via ctx.readFile()
    author.json     # optional — for Store display

The entry file must be index.js, index.ts, index.mjs, or index.cjs.

Asset files

Add other files and read them at runtime via ctx.readFile("filename") in init(ctx).

Plugin context and init()

The system calls your optional init(ctx) once at startup, before configure(). The context provides:

init(ctx) {
  // ctx.template — contents of template.html (or "")
  // ctx.dir      — absolute path to the plugin folder
  // ctx.readFile — async function to read any file from your plugin folder
}

Example: a plugin with both a command template and a separate card template (e.g. RSS uses card.html via ctx.readFile("card.html")).

Bang commands

Bang commands run when the user types !trigger something in the search bar. Export a single object (as export default or export const command) with:

Second argument to execute: context may have clientIp, page.

Optional: aliases (array of extra triggers), naturalLanguagePhrases (array of phrases that trigger without ! when natural language is on in Settings), settingsSchema and configure(settings), init(ctx), isConfigured() (async; return false to hide from !help until configured).

For settingsSchema, each field has key, label, type (text, password, url, toggle, textarea, select, urllist), and optional required, placeholder, description, secret (never sent to browser). For select, add options (array of strings).

How settings work

  1. Declare settingsSchema — a Configure button appears in Settings → Plugins.
  2. User saves; values go to data/plugin-settings.json under plugin-<folderName>.
  3. configure(settings) is called after save and on server restart if settings exist.
  4. Use isConfigured() to return false when required settings are missing — the command is then hidden from !help.
  5. Users can disable any plugin with the toggle in Settings → Plugins; disabled plugins are hidden from !help and return an error when invoked.

Examples from the official store:

Slot plugins

Slots inject panels into the search results page when the query matches. Export slot or slotPlugin (same module can also export a bang command):

Optional: settingsId — Key under which settings are stored (default slot-<id>). Optional: settingsSchema, configure(settings), init(ctx). Multiple slots can match the same query; all are shown.

Optional: slotPositions — Array of position values (e.g. ["knowledge-panel", "above-sidebar", "below-sidebar"]). If present and non-empty, the app adds a Position dropdown in the plugin settings; the user can choose which of these positions the slot uses. If empty or omitted, the slot uses the fixed position only.

Sidebar positions

"above-sidebar" — Renders at the top of the sidebar, before the main sidebar content. "below-sidebar" — Renders at the bottom of the sidebar, after engine timings and related searches.

Knowledge panel slot

position: "knowledge-panel" replaces only the first block in the sidebar (the Wikipedia/knowledge panel). Engine Performance and People also search for stay as-is. When a plugin returns content for this position, it is shown instead of the knowledge panel.

At a glance slot

Slots with position: "at-a-glance" fill the At a glance block above the search results. The search response is not delayed: the client shows results immediately and fetches at-a-glance content in a separate request (POST /api/slots/glance with body { query, results }). Your execute(query, context) receives context.results so you can use the result list (e.g. for an AI summary).

Examples from the official store:

Slot API: GET /api/slots?q=<query> returns panels for above-results, below-results, above-sidebar, below-sidebar, knowledge-panel. POST /api/slots/glance with body { query, results } returns only at-a-glance panels. Slots also run for custom tabs (e.g. weeb): the client fetches panels after a tab-search and renders them in the same positions.

Search result tabs

Search result tabs add entries to the tab bar (e.g. “Web”, “Images”). Export tab or searchResultTab from a plugin folder. Same folder structure as other plugins.

Required: id, name. Provide either engineType (string: web, images, videos, news, or a custom type from engines) or executeSearch(query, page?, context?) (async) returning { results: Array<{ title, url, snippet, source, thumbnail?, duration? }>, totalPages?: number }.

Optional: icon, settingsId, settingsSchema, configure(settings), init(ctx). When distributing via the Store, use type: "search-result-tab" in package.json and dependencies (array of URLs) if the tab needs an engine (e.g. File tab depends on Internet Archive engine). See Store.

API: GET /api/search-tabs returns the tab list; GET /api/tab-search?tab=<id>&q=<query>&page=<n> runs the tab’s search.

Search bar actions

Plugins can add buttons next to the search bar. Export searchBarActions — an array of objects with id, label, type: "navigate" (open URL; provide url), "bang" (fill bar with !trigger ; provide trigger), or "custom" (your script.js listens for search-bar-action event with detail: { actionId, inputId, input }). Optional: icon (image URL).

Plugin routes

Plugins can expose HTTP endpoints under /api/plugin/<folderName>/.... Export routes — array of { method, path, handler(req) }. method: get, post, put, delete, patch. path is the segment after the plugin id. handler receives the standard Request and returns Response or Promise<Response>. Routes are available as soon as the plugin is loaded; script.js can call them with fetch("/api/plugin/my-plugin/...").

Request middleware

Middleware lets a plugin hook into specific request flows. Export middleware — object with id, name, handle(req, context). context can include { route: "settings-auth" } etc. Return a Response, { redirect: url }, or null to continue. The app uses middleware for the settings gate. To select a plugin for the gate, use “Use as settings gate” in that plugin’s Configure in Settings → Plugins; that sets middleware.settingsGate in plugin-settings.json to plugin:<folderName>.

Settings gate (login flow)

Settings can be protected with a password (DEGOOG_SETTINGS_PASSWORDS) or a middleware plugin: when the user opens Settings, they go through your plugin’s flow (e.g. OIDC, magic link), then back to Settings with a session token.

What your middleware must do

Expose a GET /login route that redirects to /api/settings/auth/callback?returnTo=/settings. Clear degoog-settings-token in Session Storage to test.

Example: minimal bang command

Folder: data/plugins/greeting/ with index.js, template.html, style.css.

let template = "";

export default {
  name: "Greeting",
  description: "Say hello",
  trigger: "hello",
  init(ctx) { template = ctx.template; },
  async execute(args) {
    const name = args.trim() || "world";
    const html = template.replace("{{name}}", name);
    return { title: "Hello", html };
  },
};

template.html

<div class="command-result greeting">
  <h3 class="greeting-title">Hello, {{name}}!</h3>
</div>

style.css — use var(--text-primary) etc.; see Styling.