#5510·deer-flow

RFC: Frontend Dynamic Plugin System

Author: zhfengCreated Sep 17, 2026Updated Sep 17, 2026
LabelsenhancementRFCneeds-triage

Summary

Proposes a frontend dynamic plugin system that allows Python backend extensions to also contribute UI components (panels, settings pages, sidebar widgets) that are discovered and loaded at runtime — without requiring frontend code changes or rebuilds.

Motivation

DeerFlow's backend has a mature extension system allowing operators to drop in Python packages that contribute middleware, task lifecycle hooks, services, and FastAPI routers. However, the frontend has no equivalent mechanism — all UI extension points are static and require code changes + rebuilds.

This creates an asymmetry: backend behavior is pluggable, but frontend UI is not. Operators who want to customize the UI (dashboards, debugging tools, domain-specific panels) must fork the frontend.

Goals

  1. Allow backend extensions to also declare UI components in their Python package
  2. Enable runtime discovery of available UI components via the Gateway API
  3. Keep frontend asset build pipeline separate from the Python extension author workflow
  4. Maintain security boundaries (no arbitrary code execution, authenticated access)
  5. Follow existing extension patterns so the mental model is consistent with the backend

Non-Goals

  • This RFC does not cover skill/package management UI (already exists via /api/skills)
  • This RFC does not cover building a full app store or marketplace
  • This RFC does not require the frontend to be bundled with extension code

Proposed Architecture

Overview

Extension Package                          Gateway (Backend)                     Frontend (Next.js)
deerflow-extension-example/           →    1. Loads Python extension           →   1. Calls GET /api/extensions
├── __init__.py                               via install()                          to discover plugins
├── ui_manifest.json                    →    2. Serves ui_manifest.json          →   2. Fetches manifest
└── static/dist/bundle.js                     via /api/extensions/{name}/              for each extension
                                             3. Serves static assets               →   3. Dynamically imports
                                                 via /api/extensions/{name}/              JS bundle from Gateway
                                                 static/                               4. Registers components
                                                                                        with plugin registry
                                                                                     5. Renders in appropriate
                                                                                        slots

Extension Manifest Format

Each extension with UI components includes a ui_manifest.json at the package root:

json
{
  "$schema": "https://docs.deerflow.ai/schemas/ui-manifest.json",
  "name": "example",
  "version": "0.1.0",
  "components": [
    {
      "id": "example-panel",
      "type": "panel",
      "label": "Example Panel",
      "icon": "chart-bar",
      "entry": "static/dist/bundle.js",
      "config_schema": {
        "type": "object",
        "properties": {
          "apiKey": { "type": "string", "secret": true }
        }
      }
    }
  ]
}

Supported Component Types

Type Description Render Location
panel A custom panel in the right panel area Chat page right panel (alongside artifacts, browser view)
settings_page A settings configuration page Settings dialog
sidebar_widget A widget in the workspace sidebar Workspace sidebar

API Endpoints

GET /api/extensions

Returns all loaded extensions that have UI components.

json
{
  "extensions": [
    {
      "name": "example",
      "version": "0.1.0",
      "ui_components": [
        {
          "id": "example-panel",
          "type": "panel",
          "label": "Example Panel",
          "icon": "chart-bar"
        }
      ],
      "config_schema": { "type": "object", "properties": {} }
    }
  ]
}

GET /api/extensions/{name}/ui_manifest.json

Returns the raw UI manifest for a specific extension.

GET /api/extensions/{name}/static/{path}

Serves static assets (JS bundles, images) from the extension package. Path traversal is blocked.


Bundle Build Requirements

Extension authors must:

  1. Write UI as a React component bundled with Vite/esbuild (ES2020, self-contained)
  2. Include the bundle in their Python package under static/dist/
  3. Reference the bundle path in ui_manifest.json

Example Vite config:

javascript
export default defineConfig({
  build: {
    lib: {
      entry: 'src/panel.tsx',
      formats: ['es'],
      fileName: 'bundle',
    },
    rollupOptions: {
      external: ['react'],
    },
  },
});

Security Considerations

  • No arbitrary code execution — Gateway serves files from controlled paths, no path traversal
  • Authentication — all /api/extensions/* endpoints require session auth (401 otherwise)
  • Config secrets — fields marked secret: true are redacted before sending to frontend
  • CSP — dynamic script loading must respect CSP headers (needs further investigation)

Open Questions for Community Feedback

  1. Should extension UI bundles run in iframes or the main thread?

    • Iframe isolation is safer but harder to style/communicate with
    • Main thread is simpler but a buggy bundle could break the whole app
  2. How should extensions handle state persistence?

    • Gateway API (/api/extensions/{name}/state), localStorage, or read-only?
  3. Should extensions be able to contribute route pages (new URLs)?

    • Opens security and complexity questions
  4. Should the manifest support version negotiation?

    • Or keep it simple: if manifest exists, assume compatibility
  5. How should bundle build errors be handled in development?

    • Hot reload of bundles is an open problem
  6. What component types should be in scope for MVP?

    • Panel is the natural first target; settings page and sidebar widget are secondary

Implementation Phases

Phase 1 (MVP): Gateway endpoint GET /api/extensions, static file serving, frontend plugin registry, dynamic panel loading

Phase 2: Config schema validation, secret redaction, frontend config UI, state persistence API

Phase 3: Settings page + sidebar widget component types

Phase 4: Extension scaffolding tool, development mode with hot reload


Related Documentation

Full RFC document: docs/plans/2026-09-17-frontend-dynamic-plugin-rfc.md


Looking forward to feedback from the community!