paperclipai/paperclip · error

Plugin does not declare a UI bundle

Error message

Plugin does not declare a UI bundle

What it means

Returned as HTTP 404 by GET /_plugins/:pluginId/ui/* (server/src/routes/plugin-ui-static.ts:278) when the plugin exists and is 'ready', but its stored manifest has no entrypoints.ui field. Per PLUGIN_SPEC §19.0.3 the host only serves bundles for plugins that declare a UI entrypoint; without it there is nothing on disk to serve.

Source

Thrown at server/src/routes/plugin-ui-static.ts:278

      plugin = await registry.getByKey(pluginId);
    }

    if (!plugin) {
      res.status(404).json({ error: "Plugin not found" });
      return;
    }

    // Step 2: Verify the plugin is ready and has UI declared
    if (plugin.status !== "ready") {
      res.status(403).json({
        error: `Plugin UI is not available (status: ${plugin.status})`,
      });
      return;
    }

    const manifest = plugin.manifestJson;
    if (!manifest?.entrypoints?.ui) {
      res.status(404).json({ error: "Plugin does not declare a UI bundle" });
      return;
    }

    const rawCompanyId = req.query.companyId;
    if (
      Array.isArray(rawCompanyId) ||
      (rawCompanyId !== undefined && typeof rawCompanyId !== "string")
    ) {
      throw badRequest('"companyId" must be a string when provided');
    }
    const companyId = typeof rawCompanyId === "string" ? rawCompanyId.trim() : "";
    if (companyId) {
      assertCompanyAccess(req, companyId);
    }

    // Step 2b: Check for devUiUrl in company-scoped plugin config — proxy to
    // local dev server when a plugin author has configured hot-reload.
    // See PLUGIN_SPEC.md §27.2 — Local Development Workflow

View on GitHub (pinned to 120ae5428f)

Solutions

  1. Fetch the plugin record and confirm manifestJson.entrypoints.ui exists before requesting /_plugins/:id/ui/* assets
  2. If the plugin should have a UI, add entrypoints.ui (e.g. "./dist/ui/") to its manifest, rebuild, and reinstall the plugin
  3. In the host UI, only render extension slots for plugins whose manifest declares entrypoints.ui
  4. Verify you are using the correct pluginId/pluginKey — a sibling plugin without UI returns this same 404

Example fix

// before
const mod = await import(`/_plugins/${pluginId}/ui/${entry}`);

// after
const plugin = await api.getPlugin(pluginId);
if (!plugin.manifestJson?.entrypoints?.ui) {
  // Plugin has no UI bundle — skip mounting the extension slot
  return null;
}
const mod = await import(`/_plugins/${pluginId}/ui/${entry}`);
Defensive patterns

Strategy: validation

Validate before calling

const plugin = await api.getPlugin(pluginId);
const hasUi = Boolean(plugin.manifestJson?.entrypoints?.ui);
if (!hasUi) return null; // skip mounting this plugin's UI

Type guard

type UiCapablePlugin = { manifestJson: { entrypoints: { ui: string } } };
const hasUiBundle = (p: unknown): p is UiCapablePlugin =>
  Boolean((p as { manifestJson?: { entrypoints?: { ui?: string } } })
    ?.manifestJson?.entrypoints?.ui);

Prevention

When it happens

Trigger: GET /_plugins/<pluginId>/ui/<any file> for a plugin whose manifest.json lacks entrypoints.ui — e.g. a tools-only or backend-only plugin, or a plugin version published before it gained a UI. Also hit when the extension-slot renderer unconditionally mounts UI for every installed plugin instead of filtering for UI-capable ones.

Common situations: Plugin authors who never declared entrypoints.ui in their manifest; a re-published plugin whose manifest was regenerated without the ui entrypoint; host UI code that assumes all plugins ship a UI bundle; using the wrong pluginId (a sibling plugin that has no UI).

Related errors


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/52438a76007f393f. Report an issue: GitHub.