<!--
title: Unified extension API contracts
domain: ai-agents
topic: API
language: JavaScript
source: agent0ai/agent-zero
updated: 2026-02-25
url: https://awesomereviewers.com/reviewers/agent-zero-unified-extension-api-contracts/
-->

When building WebUI plugin/extension systems, treat the backend as the source of truth for (1) resolving extension contents and (2) providing a stable context schema to JS extensions.

Apply these rules:

- Extension-point resolution: When the client finds an element like `<x-extension id="...">`, it should call a dedicated backend API (e.g., `get_webui_extensions(id)`) that searches the plugin roots by convention (e.g., `.../extensions/webui/{id}/*.html`) and returns the HTML/content to paste into that node. Keep the client loader thin; avoid re-implementing manifest/scan logic when the backend can resolve by id.

- Stable context schema: Ensure `callJsExtensions(..., context)` uses one unified “context” shape across default and custom message handlers. If an extension needs `message.no` (log number), that field must be present consistently in the default implementation too.

Example (contract-driven usage):
```js
// Client: paste extension HTML resolved by backend
async function applyExtensionPoint(node, id) {
  const htmlParts = await get_webui_extensions(id); // backend contract
  node.innerHTML = htmlParts.join("\n");
}

// JS extension: rely on unified context schema
export default async function injectBranchButtons(context) {
  const msgs = context?.messages;
  if (!Array.isArray(msgs) || msgs.length === 0) return;

  for (const m of msgs) {
    // contract requires log number (no)
    if (typeof m.no !== "number") throw new Error("Missing context.messages[].no");
    // ...use m.no to wire actions
  }
}
```

Outcome: frontend extension code becomes deterministic (no “sometimes field is present” surprises), and plugin authors can build against a predictable backend-defined contract.
