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 WorkflowView on GitHub (pinned to 120ae5428f)
Solutions
- Fetch the plugin record and confirm manifestJson.entrypoints.ui exists before requesting /_plugins/:id/ui/* assets
- If the plugin should have a UI, add entrypoints.ui (e.g. "./dist/ui/") to its manifest, rebuild, and reinstall the plugin
- In the host UI, only render extension slots for plugins whose manifest declares entrypoints.ui
- 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
- Only render UI extension slots for plugins whose manifest declares entrypoints.ui
- Plugin authors: run a post-build check that dist/ui exists and the manifest references it before publishing
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
- Plugin UI directory not found
- File not found
- Plugin UI is not available (status: ${plugin.status})
- Plugin does not expose scoped API routes
- Plugin API route not found
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/52438a76007f393f.
Report an issue: GitHub.