Degoog — Plugins

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

What plugins are

Plugins live in data/plugins/ (or DEGOOG_PLUGINS_DIR). The core also loads plugins from src/commands/builtins/ using the same folder structure and rules (see Contributing). 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

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

Asset files

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

Plugin context and init()

If your plugin uses template.html, style.css, or script.js, the system loads them and passes a context to your optional init(context):

init(ctx) {
  // ctx.template — contents of template.html (or "")
  // ctx.dir      — absolute path to the plugin folder
  // ctx.readFile — async: ctx.readFile("my-data.json") reads from the plugin folder
}

init() runs once at startup, before configure().

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:

CommandResult shape:

{ title: string, html: string, totalPages?: number }

CommandContext (second argument to execute):

{ clientIp?: string, page?: number }

Optional: aliases (string[] — extra triggers), naturalLanguagePhrases (string[] — phrases that trigger this command without ! when natural language is on in Settings), settingsSchema and configure(settings) for Settings → Plugins, init(ctx), isConfigured() (async, return false to hide from !help until configured).

SettingField (for settingsSchema):

{
  key: string,
  label: string,
  type: "text" | "password" | "url" | "toggle" | "textarea",
  required?: boolean,
  placeholder?: string,
  description?: string,
  secret?: boolean   // never sent to browser; server-side only
}

How settings work

  1. Declare settingsSchema — a Configure button appears in Settings → Plugins.
  2. User saves the form; 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 (string) — Key under which settings are stored in plugin-settings.json. Default is slot-<id>. Use settingsId to keep an existing settings key (e.g. settingsId: "ai-summary").

Optional: settingsSchema, configure(settings), init(ctx). Multiple slots can match the same query; all are shown.

At a glance slot

Slots with position: "at-a-glance" fill the At a glance block above the search results (the snippet that would otherwise show the top result). Use this for summaries, AI-generated blurbs, or other content that benefits from the search result list.

Behaviour: The search response is not delayed by at-a-glance slots. 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). Until the glance request returns, an animated placeholder is shown; if no panel is returned (e.g. disabled or error), the default “first result” glance is shown.

Example (built-in): AI Summary is a slot in src/commands/builtins/ai-summary/ with position: "at-a-glance" and settingsId: "ai-summary". When enabled, it calls an OpenAI-compatible API with the query and result snippets and replaces the glance with a short AI summary.

Example from the official store: TMDb (imdb-slot) — slot plugin that shows movie/TV details above search results.

Slot API: GET /api/slots?q=<query> returns panels for above-results, below-results, and sidebar (at-a-glance slots are excluded so the search response is fast). POST /api/slots/glance with body { query, results } returns only at-a-glance panels; the client calls this after the search response to fill the glance without blocking results.

Search bar actions

Plugins can add buttons or links next to the search bar (home and results). Export searchBarActions — an array of objects with this shape:

For custom, the core dispatches a search-bar-action CustomEvent with detail: { actionId, inputId, input }. Your script can open a modal, file picker, or call your plugin route.

Plugin-registered routes

Plugins can expose HTTP API under /api/plugin/<folderName>/.... Export routes — array of:

{ method: "get"|"post"|"put"|"delete"|"patch", path: string, handler(req) }

path is the segment after the plugin id (e.g. "search"/api/plugin/my-plugin/search). handler receives the standard Request and returns Response or Promise<Response>. Use for custom APIs, callbacks, or serving HTML/JSON.

Request middleware

Middleware lets a plugin hook into specific request flows. Export middleware — object with id, name, and handle(req, context). context can include { route: "settings-auth" } etc. Return:

The core uses middleware for the settings gate (who can open Settings). To select a plugin for the gate, use the “Use as settings gate” toggle in that plugin’s Configure in Settings → Plugins; that sets middleware.settingsGate in plugin-settings.json to plugin:<folderName>.

Settings gate (login flow)

By default, Settings can be protected with a password (DEGOOG_SETTINGS_PASSWORDS). Alternatively, a middleware plugin can be the settings gate: when the user opens Settings, they are sent through your plugin’s flow (e.g. OIDC, magic link), then back to Settings with a session token.

How to enable it

  1. Add a plugin that exports middleware and implements the settings-auth routes below.
  2. In Settings → Plugins, open Configure for that plugin.
  3. Turn on “Use as settings gate” and save.

What your middleware must do

Your plugin can expose a GET /login route that shows a page and redirects the browser to /api/settings/auth/callback?returnTo=/settings. The core then attaches the token and sends the user to Settings.

How to verify it works

Clear the settings token: DevTools → Application → Session Storage → remove degoog-settings-token. Open Settings. You should be redirected to your plugin’s login, then back to the full Settings page.

Example: minimal plugin with template

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

index.js

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

.greeting-title {
  color: var(--text-primary);
  font-size: 1.5rem;
}