Skip to content

Integrations

Ng Guoyou edited this page Jul 13, 2026 · 11 revisions

How other extensions, scripts, and AI agents drive Save in. It listens on browser.runtime.onMessageExternal for other extensions and onMessage for same-extension callers, and — experimentally — registers WebMCP tools. External discovery is public to installed extensions, but downloads are denied unless the user allows the caller's extension ID.

Extension IDs are platform-specific:

  • Chrome Web Store: jpblofcpgfjikaapfedldfeilmpgkedf
  • Firefox: {72d92df5-2aa0-4b06-b807-aa21767545cd}

The options page shows the ID of the installed build. Other extensions must send to the ID for the browser they are running in.

User-configurable recipes are collected on this page. If you maintain another extension, use the separate extension integration guide.


Download API (v1)

Push a URL into the routing/rename pipeline. PING first to negotiate the version.

Before sending DOWNLOAD, paste the calling extension's exact runtime ID under Save In → Advanced → External integrations → Approved extensions and select Allow. This is not Save In's destination ID shown above. Approved IDs appear as removable rows; a bulk line editor remains available under Advanced: edit IDs as text. The allowlist is empty by default; an unlisted caller receives UNAUTHORIZED before Save In resolves the active tab or starts a download. An integration should display its own browser.runtime.id so the user can copy it. Firefox users can also find IDs in about:debugging#/runtime/this-firefox; Chrome users can enable Developer mode on chrome://extensions.

// Choose the ID for the browser running the calling extension.
const ID = "jpblofcpgfjikaapfedldfeilmpgkedf"; // Chrome
// const ID = "{72d92df5-2aa0-4b06-b807-aa21767545cd}"; // Firefox

// Discover the version and capabilities
await browser.runtime.sendMessage(ID, { type: "PING" });
// -> { type: "PONG", body: { version: 1, capabilities: [...] } }

// Save a URL
await browser.runtime.sendMessage(ID, {
  type: "DOWNLOAD",
  body: {
    url: "https://example.com/pic.jpg",
    comment: "foo", // optional; matched by `comment:` rules
    info: {
      // all optional
      pageUrl: "https://example.com/",
      srcUrl: "https://example.com/pic.jpg",
      selectionText: "…",
    },
  },
});
// -> { type: "DOWNLOAD", body: { status: "OK", version, url } }
//  | { type: "DOWNLOAD", body: { status: "ERROR", error, message, version } }
  • error is UNAUTHORIZED, BAD_REQUEST, or INVALID_URL; unknown message types return UNKNOWN_TYPE.
  • Treat UNAUTHORIZED as a request for user configuration, not as a transient failure to retry repeatedly.
  • Only http(s), ftp, data, and blob URLs are accepted.
  • The download runs through the same routing rules, :variables:, Referer feature, auto-retry, history, and notifications as a context-menu save. status: "OK" means accepted, not finished.

For a non-private rejected request, Save In shows an External download blocked notification. Clicking it opens Options, where Advanced → External integrations → Pending approval lists the caller ID, attempt count, request kind, and last-seen time. Select Approve to append that exact caller ID to the approved list and clear the rejection. The review list keeps at most 20 callers and never stores the rejected URL. Private-window rejections are neither recorded nor notified.

Callers that cannot construct a dynamic URL can explicitly target the active tab. Check that PING advertises active_tab, then send:

await browser.runtime.sendMessage(ID, {
  type: "DOWNLOAD",
  body: {
    version: 1,
    target: "activeTab",
    comment: "my-extension",
  },
});

Save In prefers the originating tab when the message came from a tab. Messages sent by another extension's background context use the active tab in the last-focused browser window. An explicit url takes precedence when both url and target are present.


Foxy Gestures

Firefox only.

  1. Find Foxy Gestures' own Extension ID in about:debugging#/runtime/this-firefox, paste it under Save In's Approved extensions, and select Allow.

  2. Foxy Gestures → options → User Scripts → new gesture.

  3. Use this script (the destination ID is Save in's Mozilla Addons ID):

    const source = data.element.mediaInfo && data.element.mediaInfo.source;
    
    if (source) {
      browser.runtime.sendMessage("{72d92df5-2aa0-4b06-b807-aa21767545cd}", {
        type: "DOWNLOAD",
        body: {
          url: source,
          info: { pageUrl: `${window.location}`, srcUrl: source },
          comment: "foo",
        },
      });
    }
  4. Assign a gesture to the script.

comment is targetable in routing rules:

comment: foo
into: ./from-foxy/:filename:

Gesturefy

Firefox only. Gesturefy has a built-in cross-add-on command, so no user script is required.

  1. Find Gesturefy's own Extension ID in about:debugging#/runtime/this-firefox, paste it under Save In's Approved extensions, and select Allow.

  2. Open Gesturefy's settings and create or edit a gesture.

  3. Select Send message to other add-on as its command.

  4. Set Add-on ID to {72d92df5-2aa0-4b06-b807-aa21767545cd}.

  5. Set Message to:

    { "type": "DOWNLOAD", "body": { "version": 1, "target": "activeTab", "comment": "gesturefy" } }
  6. Turn Parse message on, save the gesture, and try it on an ordinary web page.

Route these saves independently with:

comment: gesturefy
into: ./from-gesturefy/:filename:

The command cannot run on Firefox-restricted pages such as about: pages or Mozilla's add-on site.


Tridactyl

Firefox only. Tridactyl can bind an Ex command that messages Save In from its background context. First find Tridactyl's own Extension ID in about:debugging#/runtime/this-firefox, paste it under Save In's Approved extensions, and select Allow. Then run these two commands in Tridactyl's command line:

:command savein jsb browser.runtime.sendMessage("{72d92df5-2aa0-4b06-b807-aa21767545cd}", {type: "DOWNLOAD", body: {version: 1, target: "activeTab", comment: "tridactyl"}})
:bind ,s savein

Press ,s to route and save the active page. Choose another key sequence if that binding is already in use. For persistent dotfile configuration, put the same commands without their leading : characters in tridactylrc.

Route these saves independently with:

comment: tridactyl
into: ./from-tridactyl/:filename:

Like other WebExtensions, Tridactyl cannot invoke this binding from browser-restricted pages.


Config API

Read the schema, validate paths/rules, and apply config — the basis for scripted and AI-assisted setup.

  • GET_SCHEMA{ version, options: [{ name, type, default, description }] }. Read-only.
  • VALIDATE { paths?, filenamePatterns? }{ pathErrors, ruleErrors, menuPreview }. Dry-run, read-only.
  • GET_KEYWORDS{ variables, matchers } — the :variables: (paths/filenames) and clause matchers (fileext, into, capture, … for Dynamic Downloads rules).
  • APPLY_CONFIG { config: { name: value } }{ applied, rejected }. Validated against the schema (unknown keys and type mismatches rejected). Same-extension only — not reachable via onMessageExternal.

AI agents (WebMCP, experimental)

In a WebMCP-capable Chrome, the options page registers five tools an in-browser agent can call.

Tool Purpose Input
save_in_get_schema List every option with its type, default, and description {}
save_in_list_vocabulary List :variables: and Dynamic Downloads matchers {}
save_in_validate_config Dry-run paths/rules and optionally trace a sample download { paths?, filenamePatterns?, info? }
save_in_apply_config Validate and store a partial configuration { config: { optionName: value } }
save_in_download Save a URL through the normal routing/rename pipeline { url, pageUrl?, comment? }

Connect

  1. In Chrome, enable chrome://flags/#enable-webmcp-testing and relaunch. Origin-trial builds can use their trial enrollment instead.
  2. Open Save In's options page and keep the tab open.
  3. Under Advanced → External integrations → WebMCP, confirm Active — 5 tools registered.
  4. Point your in-browser agent at the tab.

Ask in plain language — e.g. "sort my downloads into folders by website and date, number each file, and notify me only on failures." The recommended flow is:

  1. save_in_get_schema
  2. save_in_list_vocabulary
  3. save_in_validate_config
  4. Fix any pathErrors or ruleErrors
  5. save_in_apply_config

Example validation input for a positive fileext trace:

{
  "filenamePatterns": "fileext: jpg\ninto: gallery/:sourcedomain:/:filename:",
  "info": {
    "srcUrl": "https://example.test/photo.jpg",
    "filename": "photo.jpg",
    "pageUrl": "https://example.test/gallery"
  }
}

Use srcUrl for fileext matching. If the sample only has url, use the urlfileext matcher. A successful trace reports the selected rule and the expanded, sanitized final path.

save_in_apply_config is partial: omitted options stay unchanged; omission does not delete a stored key. To restore an option, apply the default returned by save_in_get_schema. Unknown options, malformed tool arguments, and type mismatches are rejected.

For manual debugging, document.modelContext.executeTool(tool, args) expects args as a JSON string. Chrome may also return structured results as JSON text, so debugging clients should decode either form. Normal agents handle this transport detail themselves.

The tools are annotated so agents can distinguish read-only operations from config/download mutations and inputs that may contain untrusted page data. The API is still subject to change and the tools remain available only while the options tab is open.


Trust model

  • The browser can deliver onMessageExternal discovery requests from installed extensions, but Save In checks the browser-authenticated sender.id against the user's exact allowlist before resolving an active tab or triggering a download.
  • The allowlist is empty by default. Only add extensions you trust to choose URLs and initiate downloads through Save In's configured routing and fetch behavior.
  • Rejected non-private callers appear in a bounded local review list; Save In stores only caller ID, request kind, count, and time—not the rejected URL. Private-window rejections leave no review item or notification.
  • APPLY_CONFIG is deliberately internal-only.
  • No externally_connectable block, so web pages can't message Save in directly — only extensions can.

Clone this wiki locally